| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <libflash/libflash.h> | |
| #include <libflash/libflash-priv.h> | |
| #include "../libflash.c" | |
| #define __unused __attribute__((unused)) | |
| #define ERR(fmt...) fprintf(stderr, fmt) | |
| /* Flash commands */ | |
| #define CMD_PP 0x02 | |
| #define CMD_READ 0x03 | |
| #define CMD_WRDI 0x04 | |
| #define CMD_RDSR 0x05 | |
| #define CMD_WREN 0x06 | |
| #define CMD_SE 0x20 | |
| #define CMD_RDSCUR 0x2b | |
| #define CMD_BE32K 0x52 | |
| #define CMD_CE 0x60 | |
| #define CMD_RDID 0x9f | |
| #define CMD_EN4B 0xb7 | |
| #define CMD_BE 0xd8 | |
| #define CMD_RDDPB 0xe0 | |
| #define CMD_RDSPB 0xe2 | |
| #define CMD_EX4B 0xe9 | |
| /* Flash status bits */ | |
| #define STAT_WIP 0x01 | |
| #define STAT_WEN 0x02 | |
| static uint8_t *sim_image; | |
| static uint32_t sim_image_sz = 0x100000; | |
| static uint32_t sim_index; | |
| static uint32_t sim_addr; | |
| static uint32_t sim_er_size; | |
| static uint8_t sim_sr; | |
| static bool sim_fl_4b; | |
| static bool sim_ct_4b; | |
| static enum sim_state { | |
| sim_state_idle, | |
| sim_state_rdid, | |
| sim_state_rdsr, | |
| sim_state_read_addr, | |
| sim_state_read_data, | |
| sim_state_write_addr, | |
| sim_state_write_data, | |
| sim_state_erase_addr, | |
| sim_state_erase_done, | |
| } sim_state; | |
| /* | |
| * Simulated flash & controller | |
| */ | |
| static int sim_start_cmd(uint8_t cmd) | |
| { | |
| if (sim_state != sim_state_idle) { | |
| ERR("SIM: Command %02x in wrong state %d\n", cmd, sim_state); | |
| return -1; | |
| } | |
| sim_index = 0; | |
| sim_addr = 0; | |
| switch(cmd) { | |
| case CMD_RDID: | |
| sim_state = sim_state_rdid; | |
| break; | |
| case CMD_RDSR: | |
| sim_state = sim_state_rdsr; | |
| break; | |
| case CMD_EX4B: | |
| sim_fl_4b = false; | |
| break; | |
| case CMD_EN4B: | |
| sim_fl_4b = true; | |
| break; | |
| case CMD_WREN: | |
| sim_sr |= STAT_WEN; | |
| break; | |
| case CMD_READ: | |
| sim_state = sim_state_read_addr; | |
| if (sim_ct_4b != sim_fl_4b) | |
| ERR("SIM: 4b mode mismatch in READ !\n"); | |
| break; | |
| case CMD_PP: | |
| sim_state = sim_state_write_addr; | |
| if (sim_ct_4b != sim_fl_4b) | |
| ERR("SIM: 4b mode mismatch in PP !\n"); | |
| if (!(sim_sr & STAT_WEN)) | |
| ERR("SIM: PP without WEN, ignoring... \n"); | |
| break; | |
| case CMD_SE: | |
| case CMD_BE32K: | |
| case CMD_BE: | |
| if (sim_ct_4b != sim_fl_4b) | |
| ERR("SIM: 4b mode mismatch in SE/BE !\n"); | |
| if (!(sim_sr & STAT_WEN)) | |
| ERR("SIM: SE/BE without WEN, ignoring... \n"); | |
| sim_state = sim_state_erase_addr; | |
| switch(cmd) { | |
| case CMD_SE: sim_er_size = 0x1000; break; | |
| case CMD_BE32K: sim_er_size = 0x8000; break; | |
| case CMD_BE: sim_er_size = 0x10000; break; | |
| } | |
| break; | |
| case CMD_CE: | |
| if (!(sim_sr & STAT_WEN)) { | |
| ERR("SIM: CE without WEN, ignoring... \n"); | |
| break; | |
| } | |
| memset(sim_image, 0xff, sim_image_sz); | |
| sim_sr |= STAT_WIP; | |
| sim_sr &= ~STAT_WEN; | |
| break; | |
| default: | |
| ERR("SIM: Unsupported command %02x\n", cmd); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static void sim_end_cmd(void) | |
| { | |
| /* For write and sector/block erase, set WIP & clear WEN here */ | |
| if (sim_state == sim_state_write_data) { | |
| sim_sr |= STAT_WIP; | |
| sim_sr &= ~STAT_WEN; | |
| } | |
| sim_state = sim_state_idle; | |
| } | |
| static bool sim_do_address(const uint8_t **buf, uint32_t *len) | |
| { | |
| uint8_t asize = sim_fl_4b ? 4 : 3; | |
| const uint8_t *p = *buf; | |
| while(*len) { | |
| sim_addr = (sim_addr << 8) | *(p++); | |
| *buf = p; | |
| *len = *len - 1; | |
| sim_index++; | |
| if (sim_index >= asize) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static int sim_wbytes(const void *buf, uint32_t len) | |
| { | |
| const uint8_t *b = buf; | |
| bool addr_complete; | |
| again: | |
| switch(sim_state) { | |
| case sim_state_read_addr: | |
| addr_complete = sim_do_address(&b, &len); | |
| if (addr_complete) { | |
| sim_state = sim_state_read_data; | |
| sim_index = 0; | |
| if (len) | |
| goto again; | |
| } | |
| break; | |
| case sim_state_write_addr: | |
| addr_complete = sim_do_address(&b, &len); | |
| if (addr_complete) { | |
| sim_state = sim_state_write_data; | |
| sim_index = 0; | |
| if (len) | |
| goto again; | |
| } | |
| break; | |
| case sim_state_write_data: | |
| if (!(sim_sr & STAT_WEN)) | |
| break; | |
| while(len--) { | |
| uint8_t c = *(b++); | |
| if (sim_addr >= sim_image_sz) { | |
| ERR("SIM: Write past end of flash\n"); | |
| return -1; | |
| } | |
| /* Flash write only clears bits */ | |
| sim_image[sim_addr] &= c; | |
| sim_addr = (sim_addr & 0xffffff00) | | |
| ((sim_addr + 1) & 0xff); | |
| } | |
| break; | |
| case sim_state_erase_addr: | |
| if (!(sim_sr & STAT_WEN)) | |
| break; | |
| addr_complete = sim_do_address(&b, &len); | |
| if (addr_complete) { | |
| memset(sim_image + sim_addr, 0xff, sim_er_size); | |
| sim_sr |= STAT_WIP; | |
| sim_sr &= ~STAT_WEN; | |
| sim_state = sim_state_erase_done; | |
| } | |
| break; | |
| default: | |
| ERR("SIM: Write in wrong state %d\n", sim_state); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int sim_rbytes(void *buf, uint32_t len) | |
| { | |
| uint8_t *b = buf; | |
| switch(sim_state) { | |
| case sim_state_rdid: | |
| while(len--) { | |
| switch(sim_index) { | |
| case 0: | |
| *(b++) = 0x55; | |
| break; | |
| case 1: | |
| *(b++) = 0xaa; | |
| break; | |
| case 2: | |
| *(b++) = 0x55; | |
| break; | |
| default: | |
| ERR("SIM: RDID index %d\n", sim_index); | |
| *(b++) = 0; | |
| break; | |
| } | |
| sim_index++; | |
| } | |
| break; | |
| case sim_state_rdsr: | |
| while(len--) { | |
| *(b++) = sim_sr; | |
| if (sim_index > 0) | |
| ERR("SIM: RDSR index %d\n", sim_index); | |
| sim_index++; | |
| /* If WIP was 1, clear it, ie, simulate write/erase | |
| * completion | |
| */ | |
| sim_sr &= ~STAT_WIP; | |
| } | |
| break; | |
| case sim_state_read_data: | |
| while(len--) { | |
| if (sim_addr >= sim_image_sz) { | |
| ERR("SIM: Read past end of flash\n"); | |
| return -1; | |
| } | |
| *(b++) = sim_image[sim_addr++]; | |
| } | |
| break; | |
| default: | |
| ERR("SIM: Read in wrong state %d\n", sim_state); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int sim_send_addr(uint32_t addr) | |
| { | |
| const void *ap; | |
| /* Layout address MSB first in memory */ | |
| addr = cpu_to_be32(addr); | |
| /* Send the right amount of bytes */ | |
| ap = (char *)&addr; | |
| if (sim_ct_4b) | |
| return sim_wbytes(ap, 4); | |
| else | |
| return sim_wbytes(ap + 1, 3); | |
| } | |
| static int sim_cmd_rd(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, | |
| bool has_addr, uint32_t addr, void *buffer, | |
| uint32_t size) | |
| { | |
| int rc; | |
| rc = sim_start_cmd(cmd); | |
| if (rc) | |
| goto bail; | |
| if (has_addr) { | |
| rc = sim_send_addr(addr); | |
| if (rc) | |
| goto bail; | |
| } | |
| if (buffer && size) | |
| rc = sim_rbytes(buffer, size); | |
| bail: | |
| sim_end_cmd(); | |
| return rc; | |
| } | |
| static int sim_cmd_wr(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, | |
| bool has_addr, uint32_t addr, const void *buffer, | |
| uint32_t size) | |
| { | |
| int rc; | |
| rc = sim_start_cmd(cmd); | |
| if (rc) | |
| goto bail; | |
| if (has_addr) { | |
| rc = sim_send_addr(addr); | |
| if (rc) | |
| goto bail; | |
| } | |
| if (buffer && size) | |
| rc = sim_wbytes(buffer, size); | |
| bail: | |
| sim_end_cmd(); | |
| return rc; | |
| } | |
| static int sim_set_4b(struct spi_flash_ctrl *ctrl __unused, bool enable) | |
| { | |
| sim_ct_4b = enable; | |
| return 0; | |
| } | |
| static int sim_read(struct spi_flash_ctrl *ctrl __unused, uint32_t pos, | |
| void *buf, uint32_t len) | |
| { | |
| if (sim_ct_4b != sim_fl_4b) | |
| ERR("SIM: 4b mode mismatch in autoread !\n"); | |
| if ((pos + len) < pos) | |
| return -1; | |
| if ((pos + len) > sim_image_sz) | |
| return -1; | |
| memcpy(buf, sim_image + pos, len); | |
| return 0; | |
| }; | |
| struct spi_flash_ctrl sim_ctrl = { | |
| .cmd_wr = sim_cmd_wr, | |
| .cmd_rd = sim_cmd_rd, | |
| .set_4b = sim_set_4b, | |
| .read = sim_read, | |
| }; | |
| int main(void) | |
| { | |
| struct flash_chip *fl; | |
| uint32_t total_size, erase_granule; | |
| const char *name; | |
| uint16_t *test; | |
| int i, rc; | |
| sim_image = malloc(sim_image_sz); | |
| memset(sim_image, 0xff, sim_image_sz); | |
| test = malloc(0x10000 * 2); | |
| rc = flash_init(&sim_ctrl, &fl); | |
| if (rc) { | |
| ERR("flash_init failed with err %d\n", rc); | |
| exit(1); | |
| } | |
| rc = flash_get_info(fl, &name, &total_size, &erase_granule); | |
| if (rc) { | |
| ERR("flash_get_info failed with err %d\n", rc); | |
| exit(1); | |
| } | |
| /* Make up a test pattern */ | |
| for (i=0; i<0x10000;i++) | |
| test[i] = cpu_to_be16(i); | |
| /* Write 64k of stuff at 0 and at 128k */ | |
| printf("Writing test patterns...\n"); | |
| flash_smart_write(fl, 0, test, 0x10000); | |
| flash_smart_write(fl, 0x20000, test, 0x10000); | |
| /* Write "Hello world" straddling the 64k boundary */ | |
| #define HW "Hello World" | |
| printf("Writing test string...\n"); | |
| flash_smart_write(fl, 0xfffc, HW, sizeof(HW)); | |
| /* Check result */ | |
| if (memcmp(sim_image + 0xfffc, HW, sizeof(HW))) { | |
| ERR("Test string mismatch !\n"); | |
| exit(1); | |
| } | |
| printf("Test string pass\n"); | |
| if (memcmp(sim_image, test, 0xfffc)) { | |
| ERR("Test pattern mismatch !\n"); | |
| exit(1); | |
| } | |
| printf("Test pattern pass\n"); | |
| flash_exit(fl); | |
| return 0; | |
| } | |