static int sandbox_sf_xfer(struct udevice *dev, unsigned int bitlen, const void *rxp, void *txp, unsigned long flags) { struct sandbox_spi_flash *sbsf = dev_get_priv(dev); const uint8_t *rx = rxp; uint8_t *tx = txp; uint cnt, pos = 0; int bytes = bitlen / 8; int ret; debug("sandbox_sf: state:%x(%s) bytes:%u\n", sbsf->state, sandbox_sf_state_name(sbsf->state), bytes); if ((flags & SPI_XFER_BEGIN)) sandbox_sf_cs_activate(dev); if (sbsf->state == SF_CMD) { /* Figure out the initial state */ ret = sandbox_sf_process_cmd(sbsf, rx, tx); if (ret) return ret; ++pos; } /* Process the remaining data */ while (pos < bytes) { switch (sbsf->state) { case SF_ID: { u8 id; debug(" id: off:%u tx:", sbsf->off); if (sbsf->off < IDCODE_LEN) { /* Extract correct byte from ID 0x00aabbcc */ id = sbsf->data->jedec >> (8 * (IDCODE_LEN - 1 - sbsf->off)); } else { id = 0; } debug("%d %02x\n", sbsf->off, id); tx[pos++] = id; ++sbsf->off; break; } case SF_ADDR: debug(" addr: bytes:%u rx:%02x ", sbsf->addr_bytes, rx[pos]); if (sbsf->addr_bytes++ < SF_ADDR_LEN) sbsf->off = (sbsf->off << 8) | rx[pos]; debug("addr:%06x\n", sbsf->off); if (tx) sandbox_spi_tristate(&tx[pos], 1); pos++; /* See if we're done processing */ if (sbsf->addr_bytes < SF_ADDR_LEN + sbsf->pad_addr_bytes) break; /* Next state! */ if (os_lseek(sbsf->fd, sbsf->off, OS_SEEK_SET) < 0) { puts("sandbox_sf: os_lseek() failed"); return -EIO; } switch (sbsf->cmd) { case CMD_READ_ARRAY_FAST: case CMD_READ_ARRAY_SLOW: sbsf->state = SF_READ; break; case CMD_PAGE_PROGRAM: sbsf->state = SF_WRITE; break; default: /* assume erase state ... */ sbsf->state = SF_ERASE; goto case_sf_erase; } debug(" cmd: transition to %s state\n", sandbox_sf_state_name(sbsf->state)); break; case SF_READ: /* * XXX: need to handle exotic behavior: * - reading past end of device */ cnt = bytes - pos; debug(" tx: read(%u)\n", cnt); assert(tx); ret = os_read(sbsf->fd, tx + pos, cnt); if (ret < 0) { puts("sandbox_sf: os_read() failed\n"); return -EIO; } pos += ret; break; case SF_READ_STATUS: debug(" read status: %#x\n", sbsf->status); cnt = bytes - pos; memset(tx + pos, sbsf->status, cnt); pos += cnt; break; case SF_READ_STATUS1: debug(" read status: %#x\n", sbsf->status); cnt = bytes - pos; memset(tx + pos, sbsf->status >> 8, cnt); pos += cnt; break; case SF_WRITE_STATUS: debug(" write status: %#x (ignored)\n", rx[pos]); pos = bytes; break; case SF_WRITE: /* * XXX: need to handle exotic behavior: * - unaligned addresses * - more than a page (256) worth of data * - reading past end of device */ if (!(sbsf->status & STAT_WEL)) { puts("sandbox_sf: write enable not set before write\n"); goto done; } cnt = bytes - pos; debug(" rx: write(%u)\n", cnt); if (tx) sandbox_spi_tristate(&tx[pos], cnt); ret = os_write(sbsf->fd, rx + pos, cnt); if (ret < 0) { puts("sandbox_spi: os_write() failed\n"); return -EIO; } pos += ret; sbsf->status &= ~STAT_WEL; break; case SF_ERASE: case_sf_erase: { if (!(sbsf->status & STAT_WEL)) { puts("sandbox_sf: write enable not set before erase\n"); goto done; } /* verify address is aligned */ if (sbsf->off & (sbsf->erase_size - 1)) { debug(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n", sbsf->cmd, sbsf->erase_size, sbsf->off); sbsf->status &= ~STAT_WEL; goto done; } debug(" sector erase addr: %u, size: %u\n", sbsf->off, sbsf->erase_size); cnt = bytes - pos; if (tx) sandbox_spi_tristate(&tx[pos], cnt); pos += cnt; /* * TODO([email protected]): latch WIP in status, and * delay before clearing it ? */ ret = sandbox_erase_part(sbsf, sbsf->erase_size); sbsf->status &= ~STAT_WEL; if (ret) { debug("sandbox_sf: Erase failed\n"); goto done; } goto done; } default: debug(" ??? no idea what to do ???\n"); goto done; }
/* Figure out what command this stream is telling us to do */ static int sandbox_sf_process_cmd(struct sandbox_spi_flash *sbsf, const u8 *rx, u8 *tx) { enum sandbox_sf_state oldstate = sbsf->state; /* We need to output a byte for the cmd byte we just ate */ if (tx) sandbox_spi_tristate(tx, 1); sbsf->cmd = rx[0]; switch (sbsf->cmd) { case CMD_READ_ID: sbsf->state = SF_ID; sbsf->cmd = SF_ID; break; case CMD_READ_ARRAY_FAST: sbsf->pad_addr_bytes = 1; case CMD_READ_ARRAY_SLOW: case CMD_PAGE_PROGRAM: sbsf->state = SF_ADDR; break; case CMD_WRITE_DISABLE: debug(" write disabled\n"); sbsf->status &= ~STAT_WEL; break; case CMD_READ_STATUS: sbsf->state = SF_READ_STATUS; break; case CMD_READ_STATUS1: sbsf->state = SF_READ_STATUS1; break; case CMD_WRITE_ENABLE: debug(" write enabled\n"); sbsf->status |= STAT_WEL; break; case CMD_WRITE_STATUS: sbsf->state = SF_WRITE_STATUS; break; default: { int flags = sbsf->data->flags; /* we only support erase here */ if (sbsf->cmd == CMD_ERASE_CHIP) { sbsf->erase_size = sbsf->data->sector_size * sbsf->data->nr_sectors; } else if (sbsf->cmd == CMD_ERASE_4K && (flags & SECT_4K)) { sbsf->erase_size = 4 << 10; } else if (sbsf->cmd == CMD_ERASE_64K && !(flags & SECT_4K)) { sbsf->erase_size = 64 << 10; } else { debug(" cmd unknown: %#x\n", sbsf->cmd); return -EIO; } sbsf->state = SF_ADDR; break; } } if (oldstate != sbsf->state) debug(" cmd: transition to %s state\n", sandbox_sf_state_name(sbsf->state)); return 0; }
/* Figure out what command this stream is telling us to do */ static int sandbox_sf_process_cmd(struct sandbox_spi_flash *sbsf, const u8 *rx, u8 *tx) { enum sandbox_sf_state oldstate = sbsf->state; /* We need to output a byte for the cmd byte we just ate */ sandbox_spi_tristate(tx, 1); sbsf->cmd = rx[0]; switch (sbsf->cmd) { case CMD_READ_ID: sbsf->state = SF_ID; sbsf->cmd = SF_ID; break; case CMD_READ_ARRAY_FAST: sbsf->pad_addr_bytes = 1; case CMD_READ_ARRAY_SLOW: case CMD_PAGE_PROGRAM: state_addr: sbsf->state = SF_ADDR; break; case CMD_WRITE_DISABLE: debug(" write disabled\n"); sbsf->status &= ~STAT_WEL; break; case CMD_READ_STATUS: sbsf->state = SF_READ_STATUS; break; case CMD_READ_STATUS1: sbsf->state = SF_READ_STATUS1; break; case CMD_WRITE_ENABLE: debug(" write enabled\n"); sbsf->status |= STAT_WEL; break; default: { size_t i; /* handle erase commands first */ for (i = 0; i < MAX_ERASE_CMDS; ++i) { const struct sandbox_spi_flash_erase_commands * erase_cmd = &sbsf->data->erase_cmds[i]; if (erase_cmd->cmd == 0x00) continue; if (sbsf->cmd != erase_cmd->cmd) continue; sbsf->cmd_data = erase_cmd; goto state_addr; } debug(" cmd unknown: %#x\n", sbsf->cmd); return 1; } } if (oldstate != sbsf->state) debug(" cmd: transition to %s state\n", sandbox_sf_state_name(sbsf->state)); return 0; }
static int sandbox_sf_xfer(void *priv, const u8 *rx, u8 *tx, uint bytes) { struct sandbox_spi_flash *sbsf = priv; uint cnt, pos = 0; int ret; debug("sandbox_sf: state:%x(%s) bytes:%u\n", sbsf->state, sandbox_sf_state_name(sbsf->state), bytes); if (sbsf->state == SF_CMD) { /* Figure out the initial state */ if (sandbox_sf_process_cmd(sbsf, rx, tx)) return 1; ++pos; } /* Process the remaining data */ while (pos < bytes) { switch (sbsf->state) { case SF_ID: { u8 id; debug(" id: off:%u tx:", sbsf->off); if (sbsf->off < IDCODE_LEN) id = sbsf->data->idcode[sbsf->off]; else id = 0; debug("%02x\n", id); tx[pos++] = id; ++sbsf->off; break; } case SF_ADDR: debug(" addr: bytes:%u rx:%02x ", sbsf->addr_bytes, rx[pos]); if (sbsf->addr_bytes++ < SF_ADDR_LEN) sbsf->off = (sbsf->off << 8) | rx[pos]; debug("addr:%06x\n", sbsf->off); sandbox_spi_tristate(&tx[pos++], 1); /* See if we're done processing */ if (sbsf->addr_bytes < SF_ADDR_LEN + sbsf->pad_addr_bytes) break; /* Next state! */ if (os_lseek(sbsf->fd, sbsf->off, OS_SEEK_SET) < 0) { puts("sandbox_sf: os_lseek() failed"); return 1; } switch (sbsf->cmd) { case CMD_READ_ARRAY_FAST: case CMD_READ_ARRAY_SLOW: sbsf->state = SF_READ; break; case CMD_PAGE_PROGRAM: sbsf->state = SF_WRITE; break; default: /* assume erase state ... */ sbsf->state = SF_ERASE; goto case_sf_erase; } debug(" cmd: transition to %s state\n", sandbox_sf_state_name(sbsf->state)); break; case SF_READ: /* * XXX: need to handle exotic behavior: * - reading past end of device */ cnt = bytes - pos; debug(" tx: read(%u)\n", cnt); ret = os_read(sbsf->fd, tx + pos, cnt); if (ret < 0) { puts("sandbox_spi: os_read() failed\n"); return 1; } pos += ret; break; case SF_READ_STATUS: debug(" read status: %#x\n", sbsf->status); cnt = bytes - pos; memset(tx + pos, sbsf->status, cnt); pos += cnt; break; case SF_READ_STATUS1: debug(" read status: %#x\n", sbsf->status); cnt = bytes - pos; memset(tx + pos, sbsf->status >> 8, cnt); pos += cnt; break; case SF_WRITE: /* * XXX: need to handle exotic behavior: * - unaligned addresses * - more than a page (256) worth of data * - reading past end of device */ if (!(sbsf->status & STAT_WEL)) { puts("sandbox_sf: write enable not set before write\n"); goto done; } cnt = bytes - pos; debug(" rx: write(%u)\n", cnt); sandbox_spi_tristate(&tx[pos], cnt); ret = os_write(sbsf->fd, rx + pos, cnt); if (ret < 0) { puts("sandbox_spi: os_write() failed\n"); return 1; } pos += ret; sbsf->status &= ~STAT_WEL; break; case SF_ERASE: case_sf_erase: { const struct sandbox_spi_flash_erase_commands * erase_cmd = sbsf->cmd_data; if (!(sbsf->status & STAT_WEL)) { puts("sandbox_sf: write enable not set before erase\n"); goto done; } /* verify address is aligned */ if (sbsf->off & (erase_cmd->size - 1)) { debug(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n", erase_cmd->cmd, erase_cmd->size, sbsf->off); sbsf->status &= ~STAT_WEL; goto done; } debug(" sector erase addr: %u\n", sbsf->off); cnt = bytes - pos; sandbox_spi_tristate(&tx[pos], cnt); pos += cnt; /* * TODO([email protected]): latch WIP in status, and * delay before clearing it ? */ ret = sandbox_erase_part(sbsf, erase_cmd->size); sbsf->status &= ~STAT_WEL; if (ret) { debug("sandbox_sf: Erase failed\n"); goto done; } goto done; } default: debug(" ??? no idea what to do ???\n"); goto done; } } done: return pos == bytes ? 0 : 1; }
/* Figure out what command this stream is telling us to do */ static int sandbox_sf_process_cmd(struct sandbox_spi_flash *sbsf, const u8 *rx, u8 *tx) { enum sandbox_sf_state oldstate = sbsf->state; /* We need to output a byte for the cmd byte we just ate */ if (tx) sandbox_spi_tristate(tx, 1); sbsf->cmd = rx[0]; switch (sbsf->cmd) { case SPINOR_OP_RDID: sbsf->state = SF_ID; sbsf->cmd = SF_ID; break; case SPINOR_OP_READ_FAST: sbsf->pad_addr_bytes = 1; case SPINOR_OP_READ: case SPINOR_OP_PP: sbsf->state = SF_ADDR; break; case SPINOR_OP_WRDI: debug(" write disabled\n"); sbsf->status &= ~STAT_WEL; break; case SPINOR_OP_RDSR: sbsf->state = SF_READ_STATUS; break; case SPINOR_OP_RDSR2: sbsf->state = SF_READ_STATUS1; break; case SPINOR_OP_WREN: debug(" write enabled\n"); sbsf->status |= STAT_WEL; break; case SPINOR_OP_WRSR: sbsf->state = SF_WRITE_STATUS; break; default: { int flags = sbsf->data->flags; /* we only support erase here */ if (sbsf->cmd == SPINOR_OP_CHIP_ERASE) { sbsf->erase_size = sbsf->data->sector_size * sbsf->data->n_sectors; } else if (sbsf->cmd == SPINOR_OP_BE_4K && (flags & SECT_4K)) { sbsf->erase_size = 4 << 10; } else if (sbsf->cmd == SPINOR_OP_SE && !(flags & SECT_4K)) { sbsf->erase_size = 64 << 10; } else { debug(" cmd unknown: %#x\n", sbsf->cmd); return -EIO; } sbsf->state = SF_ADDR; break; } } if (oldstate != sbsf->state) log_content(" cmd: transition to %s state\n", sandbox_sf_state_name(sbsf->state)); return 0; }