/* $Id: flash29.c,v 1.6 2008/07/31 01:53:44 cjohns Exp $ * * Driver for 29Fxxx and 49Fxxx flash chips. * * 2003-12-28 Josef Wolf (jw@raven.inka.de) * * Portions of this program which I authored may be used for any purpose * so long as this notice is left intact. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* This piece of code arose from ad-hoc code-snippets which I threw into discussions on the bdm-devel mailing list and in private mails to Pavel Pisa. I have put those pieces together, removed compiler-errors, rewrote them several times and optimized somewhat. This code supports: - 29Fxxx and 49Fxxx types of flash chips. - Host-only, target-assisted and target-only operation modes. - Bus widths of 1, 2 and 4 bytes. - Chip widths of 1, 2 and 4 bytes. - Autodetection of known chips on arbitrary bus/chip width combinations. - Fast bypass unlock programming mode on chips that support it. - Unaligned programming. */ /* Todo: CSI command set? What is this? CFI specification http://www.amd.com/products/nvd/overview/cfi.html */ #include "flash29.h" #include "flash_filter.h" #if HOST_FLASHING # include # include #endif /* This defines details for the flash algorithm. */ typedef struct { uint32_t cmd_unlock1; uint32_t cmd_unlock2; uint32_t cmd_reset; uint32_t cmd_autoselect; uint32_t cmd_program; uint32_t cmd_erase; uint32_t cmd_chiperase; uint32_t cmd_secterase; uint32_t cmd_bypass; uint32_t cmd_resbypass1; uint32_t cmd_resbypass2; uint32_t timeout_mask; uint32_t adr_unlock1; uint32_t adr_unlock2; } alg_info_t; typedef struct { const char *name; const uint32_t manufacturer, device_id; const uint32_t size; /* in bytes */ const alg_info_t *alg_info; } chip_t; typedef struct { const alg_info_t *alg_info; /* algorithm information */ uint32_t adr; /* base adress of the chip */ uint32_t size; /* in bytes */ uint32_t chip_width; /* 1->8bit, 2->16bit, 4->32bit */ uint32_t bus_width; /* 1->8bit, 2->16bit, 4->32bit */ uint32_t wait_adr; /* address to check when waiting for erase */ /* From here on, everything is redundant. The only reason to maintain this redundant information is optimization. */ uint32_t reg1; /* address of unlock register 1 */ uint32_t reg2; /* address of unlock register 2 */ /* This information is placed at the end of the struct because download_struct() don't need to fiddle around with it. */ void (*wr_func) (uint32_t, uint32_t); /* write function */ uint32_t(*rd_func) (uint32_t); /* read function */ uint32_t bus_mask; /* Mask to get bus_width lowest bytes */ uint32_t chip_mask; /* Mask to get chip_width lowest bytes */ uint32_t shift; /* shift for multiplication by bus_width */ } chiptype_t; static const alg_info_t alg_29_std = { /* standard 29fxx chips */ 0xaaaaaaaa, /* unlock 1 command */ 0x55555555, /* unlock 2 command */ 0xf0f0f0f0, /* res */ 0x90909090, /* asel */ 0xa0a0a0a0, /* prog */ 0x80808080, /* erase */ 0x10101010, /* chiperase */ 0x30303030, /* secterase */ 0x00000000, /* no unlock bypass */ 0x90909090, /* unlock bypass res1 */ 0x00000000, /* unlock bypass res2 */ 0x20, /* timeout bitmask */ 0x555, /* register 1 */ 0x2aa, /* register 5 */ }; static const alg_info_t alg_29_unl = { /* 29fxx with unlock bypass mode */ 0xaaaaaaaa, /* unlock 1 command */ 0x55555555, /* unlock 2 command */ 0xf0f0f0f0, /* res */ 0x90909090, /* asel */ 0xa0a0a0a0, /* prog */ 0x80808080, /* erase */ 0x10101010, /* chiperase */ 0x30303030, /* secterase */ 0x20202020, /* unlock bypass */ 0x90909090, /* unlock bypass res1 */ 0x00000000, /* unlock bypass res2 */ 0x20, /* timeout bitmask */ 0x555, /* register 1 */ 0x2aa, /* register 5 */ }; static const alg_info_t alg_49 = { /* 49fxx and 39fxx chips */ 0xaaaaaaaa, /* unlock 1 command */ 0x55555555, /* unlock 2 command */ 0xf0f0f0f0, /* res */ 0x90909090, /* asel */ 0xa0a0a0a0, /* prog */ 0x80808080, /* erase */ 0x10101010, /* chiperase */ 0x30303030, /* secterase *//* Block erase ? */ 0x00000000, /* no unlock bypass */ 0x90909090, /* unlock bypass res1 */ 0x00000000, /* unlock bypass res2 */ 0x00, /* no timeout support */ 0x5555, /* register 1 */ 0x2aaa, /* register 5 */ }; /* Here come the known chips. */ static const chip_t chips[] = { {"Am29LV001BT", 0x01, 0xed, 0x20000, &alg_29_unl}, /* AMD */ {"Am29LV001BB", 0x01, 0x6d, 0x20000, &alg_29_unl}, /* AMD */ {"Am29LV002T", 0x01, 0x40, 0x40000, &alg_29_std}, /* AMD */ {"Am29LV002B", 0x01, 0xc2, 0x40000, &alg_29_std}, /* AMD */ {"Am29LV004T", 0x01, 0xb5, 0x80000, &alg_29_std}, /* AMD */ {"Am29LV004B", 0x01, 0xb6, 0x80000, &alg_29_std}, /* AMD */ {"Am29LV008BT", 0x01, 0x3e, 0x100000, &alg_29_unl}, /* AMD */ {"Am29LV008BB", 0x01, 0x37, 0x100000, &alg_29_unl}, /* AMD */ {"Am29LV017B", 0x01, 0xc8, 0x200000, &alg_29_unl}, /* AMD */ {"Am29F010B", 0x01, 0x20, 0x20000, &alg_29_std}, /* AMD */ {"At49F040", 0x1f, 0x13, 0x80000, &alg_49}, /* Atmel */ {"Am29F040B", 0x01, 0xa4, 0x80000, &alg_29_std}, /* AMD */ {"Am29F400BT", 0x01, 0x2223, 0x80000, &alg_29_std}, /* AMD */ {"Am29F400BB", 0x01, 0x22ab, 0x80000, &alg_29_std}, /* AMD */ {"MBM29F400TC", 0x04, 0x2223, 0x80000, &alg_29_std}, /* Fujitsu */ {"MBM29F400BC", 0x04, 0x22ab, 0x80000, &alg_29_std}, /* Fujitsu */ {"M29F400BB", 0x20, 0x22d6, 0x80000, &alg_29_unl}, /* ST */ {"M29F400BB", 0x20, 0x22d5, 0x80000, &alg_29_unl}, /* ST */ {"Am29F080B", 0x01, 0xd5, 0x100000, &alg_29_std}, /* AMD */ {"Am29F800BT", 0x01, 0x22d6, 0x100000, &alg_29_std}, /* AMD */ {"Am29F800BB", 0x01, 0x2258, 0x100000, &alg_29_std}, /* AMD */ {"Am29PL160C", 0x01, 0x2245, 0x200000, &alg_29_unl}, /* AMD */ {"Am29LV160B", 0x01, 0x2249, 0x200000, &alg_29_unl}, /* AMD */ {"M29LV160B", 0x20, 0x2249, 0x200000, &alg_29_unl}, /* ST */ {"HY29LV160B", 0xad, 0x2249, 0x200000, &alg_29_unl}, /* HYRIX */ {"MX29LV160B", 0xc2, 0x2245, 0x200000, &alg_29_unl}, /* MACRONIX */ {"TC58FVB160A", 0x98, 0x0043, 0x200000, &alg_29_unl}, /* TOSHIBA */ {"Am29LV320B", 0x01, 0x22f9, 0x400000, &alg_29_unl}, /* AMD */ {"HY29LV320B", 0xad, 0x227d, 0x400000, &alg_29_unl}, /* HYRIX */ {"MX29LV320B", 0xc2, 0x22a8, 0x400000, &alg_29_unl}, /* MACRONIX */ {"TC58FVM5B2A", 0x98, 0x0055, 0x400000, &alg_29_unl}, /* TOSHIBA */ {"TC58FVM5B3A", 0x98, 0x0050, 0x400000, &alg_29_unl}, /* TOSHIBA */ {"Am29LV640MB", 0x01, 0x227e, 0x800000, &alg_29_unl}, /* AMD */ {"M29W640DB", 0x20, 0x22df, 0x800000, &alg_29_unl}, /* ST */ {"MBM29DLV640E", 0x04, 0x227e, 0x800000, &alg_29_std}, /* FUJITSU */ {"MX29LV640MB", 0xc2, 0x22cb, 0x800000, &alg_29_unl}, /* MACRONIX */ {"TC58FVM6B2A", 0x98, 0x0058, 0x800000, &alg_29_unl}, /* TOSHIBA */ {"Sst39VF1601", 0xbf, 0x234b, 0x200000, &alg_49}, /* SST */ {"Sst39VF1602", 0xbf, 0x234a, 0x200000, &alg_49}, /* SST */ {"Sst39VF3201", 0xbf, 0x235b, 0x400000, &alg_49}, /* SST */ {"Sst39VF3202", 0xbf, 0x235a, 0x400000, &alg_49}, /* SST */ {"Sst39VF6401", 0xbf, 0x236b, 0x800000, &alg_49}, /* SST */ {"Sst39VF6402", 0xbf, 0x236a, 0x800000, &alg_49}, /* SST */ }; /* define the actual access functions to the flash. There are three orthogonal aspects for the access functions: 1. Every bus_width needs its own set of functions because we need to access the chips with bus_width width. This is to avoid the access be split up into several accesses, which could abort a started command. 2. Bus accesses need two functions: one for reading and one for writing. 3. In addition, we need a second set of functions for host-assisted access. */ #if HOST_FLASHING # define DEFINE_READ_FUNC(funcname,vartype,bdmfunc) \ static uint32_t funcname (uint32_t adr) { \ vartype val; \ if (bdmfunc (adr, &val) < 0) \ fprintf (stderr, #bdmfunc "(0x%08lx,xxx): %s\n", \ adr, bdmErrorString()); \ return (uint32_t) val; \ } # define DEFINE_WRITE_FUNC(funcname,vartype,bdmfunc) \ static void funcname (uint32_t adr, uint32_t val) { \ if (bdmfunc (adr, val) < 0) \ fprintf (stderr, #bdmfunc "(0x%08lx,0x%08x): %s\n", \ adr, val, bdmErrorString()); \ } #else # define DEFINE_READ_FUNC(funcname,vartype,bdmfunc) \ static uint32_t funcname (uint32_t adr) { \ return *(volatile vartype *)adr; \ } # define DEFINE_WRITE_FUNC(funcname,vartype,bdmfunc) \ static void funcname (uint32_t adr,uint32_t val) { \ *(volatile vartype *)adr = val; \ } #endif DEFINE_READ_FUNC(chip_rd_char, unsigned char, bdmReadByte) DEFINE_READ_FUNC(chip_rd_word, unsigned short, bdmReadWord) DEFINE_READ_FUNC(chip_rd_long, unsigned long, bdmReadLongWord) DEFINE_WRITE_FUNC(chip_wr_char, uint8_t, bdmWriteByte) DEFINE_WRITE_FUNC(chip_wr_word, uint16_t, bdmWriteWord) DEFINE_WRITE_FUNC(chip_wr_long, uint32_t, bdmWriteLongWord) /* We need a unique symbol to associate a (target-based) plugin with its (host-based) driver. */ static char driver_magic[] = "flash29"; /* Populate the the chiptype structure with bus_width specific information. This information is redundant. It is maintained here only for optimizations. */ static void set_chip_access(chiptype_t * ct, uint32_t bus_width) { ct->bus_width = bus_width; switch (bus_width) { case 1: ct->shift = 0; ct->bus_mask = 0x0ff; ct->wr_func = chip_wr_char; ct->rd_func = chip_rd_char; break; case 2: ct->shift = 1; ct->bus_mask = 0x0ffff; ct->wr_func = chip_wr_word; ct->rd_func = chip_rd_word; break; case 4: ct->shift = 2; ct->bus_mask = 0x0ffffffff; ct->wr_func = chip_wr_long; ct->rd_func = chip_rd_long; break; } ct->chip_mask = (0x100 << ((ct->chip_width - 1) << 3)) - 1; } /* Wait for the chip to complete an erase/program command. Pavel Pisa mentioned that there's probably a racing condition in this code, so we will need to take a closer look on this one. */ static int wait_chip(chiptype_t * ct, uint32_t adr, uint32_t expect) { uint32_t i; uint32_t rval; uint32_t bus_mask = ct->bus_mask; uint32_t chip_mask = ct->chip_mask; uint32_t tout_mask = ct->alg_info->timeout_mask; uint32_t(*rd_func) (uint32_t) = ct->rd_func; uint32_t last_val = (rd_func(adr) & bus_mask); while ((rval = (rd_func(adr) & bus_mask)) != (expect & bus_mask)) { /* We don't want to loop forever when we accidentally try to program an EPROM. So we expect at least one toggle-bit to change. */ if (last_val == rval) return 0; /* loop over chips */ for (i = 0; i < ct->bus_width; i += ct->chip_width) { /* shift to get bits of this chip onto the lowest bits. */ uint32_t shift = i << 3; uint32_t shifted_chip_mask = chip_mask << shift; /* check whether chip i signalled timeout. */ if ((rval >> shift) & tout_mask) return 0; /* check whether chip i returned expected data. */ if (!(((rval ^ expect) & shifted_chip_mask))) { /* Yes, we're not interested in results from this chip anymore. */ bus_mask &= ~shifted_chip_mask; } } last_val = rval; } return 1; } #if HOST_FLASHING static char * prog_entry(void) { return "flash29_prog"; } #endif /* The actual programming function */ static uint32_t flash29_prog(void *chip_descr, uint32_t pos, unsigned char *data, uint32_t cnt) { uint32_t i, align; void (*wr_func) (uint32_t, uint32_t); uint32_t val = 0; chiptype_t *ct = chip_descr; uint32_t reg1 = ct->reg1; uint32_t reg2 = ct->reg2; uint32_t siz = ct->bus_width; const alg_info_t *alg_info = ct->alg_info; uint32_t cmd_bypass = alg_info->cmd_bypass; uint32_t cmd_reset = alg_info->cmd_reset; uint32_t cmd_prog = alg_info->cmd_program; uint32_t cmd_unlock1 = alg_info->cmd_unlock1; uint32_t cmd_unlock2 = alg_info->cmd_unlock2; set_chip_access(ct, siz); wr_func = ct->wr_func; /* handle unaligned programming */ align = pos & ((1 << (siz - 1)) - 1);; pos &= ~align; for (i = 0; i < align; i++) { val <<= 8; val |= chip_rd_char(pos + i); } if (cmd_bypass) { wr_func(reg1, cmd_reset); wr_func(reg1, cmd_unlock1); wr_func(reg2, cmd_unlock2); wr_func(reg1, cmd_bypass); } i = 0; #if FLASH_OPTIMIZE_FOR_SPEED switch (siz) { # if FLASH_BUS_WIDTH1 case 1: for (; i < cnt; i++) { if (!cmd_bypass) { chip_wr_char(reg1, cmd_reset); chip_wr_char(reg1, cmd_unlock1); chip_wr_char(reg2, cmd_unlock2); } chip_wr_char(reg1, cmd_prog); val = *data++; chip_wr_char(pos, val); if (!wait_chip(ct, pos, val)) break; /* error out */ pos++; } # endif # if FLASH_BUS_WIDTH2 case 2: if (align) goto W1; while (i < cnt) { val = (val << 8) | *data++; W1:val = (val << 8) | (i++ < cnt ? *data++ : chip_rd_char(pos + 1)); if (!cmd_bypass) { chip_wr_word(reg1, cmd_reset); chip_wr_word(reg1, cmd_unlock1); chip_wr_word(reg2, cmd_unlock2); } chip_wr_word(reg1, cmd_prog); chip_wr_word(pos, val); if (!wait_chip(ct, pos, val)) break; /* error out */ pos += 2; i++; val = 0; } break; # endif # if FLASH_BUS_WIDTH4 case 4: switch (align) { case 1: goto L1; case 2: goto L2; case 3: goto L3; } while (i < cnt) { val = (val << 8) | *data++; L1:val = (val << 8) | (i++ < cnt ? *data++ : chip_rd_char(pos + 1)); L2:val = (val << 8) | (i++ < cnt ? *data++ : chip_rd_char(pos + 2)); L3:val = (val << 8) | (i++ < cnt ? *data++ : chip_rd_char(pos + 3)); if (!cmd_bypass) { chip_wr_long(reg1, cmd_reset); chip_wr_long(reg1, cmd_unlock1); chip_wr_long(reg2, cmd_unlock2); } chip_wr_long(reg1, cmd_prog); chip_wr_long(pos, val); if (!wait_chip(ct, pos, val)) break; /* error out */ pos += 4; i++; val = 0; } break; # endif } #else while (i < cnt) { uint32_t j; for (j = 0; j < siz - align; j++) { val <<= 8; val |= i + j < cnt ? *data++ : chip_rd_char(pos + j); } if (!cmd_bypass) { wr_func(reg1, cmd_reset); wr_func(reg1, cmd_unlock1); wr_func(reg2, cmd_unlock2); } wr_func(reg1, cmd_prog); wr_func(pos, val); if (!wait_chip(ct, pos, val)) break; /* error out */ pos += siz; i += siz; val = 0; align = 0; } #endif if (cmd_bypass) { wr_func(reg1, ct->alg_info->cmd_resbypass1); wr_func(reg2, ct->alg_info->cmd_resbypass2); } return i > cnt ? cnt : i; } /* Initiate erase operation. Sector address is relative to chip-base. With sector address==-1, the whole chip is erased. The erase operation can be called several times before flash_29_erase_wait() is called for simultanous erasure of multiple sectors. */ static void flash29_erase(void *chip_descr, int32_t sector_address) { chiptype_t *ct = chip_descr; uint32_t reg1 = ct->reg1; uint32_t reg2 = ct->reg2; const alg_info_t *alg_info = ct->alg_info; void (*wr_func) (uint32_t, uint32_t) = ct->wr_func; wr_func(reg1, alg_info->cmd_reset); wr_func(reg1, alg_info->cmd_unlock1); wr_func(reg2, alg_info->cmd_unlock2); wr_func(reg1, alg_info->cmd_erase); wr_func(reg1, alg_info->cmd_unlock1); wr_func(reg2, alg_info->cmd_unlock2); if (sector_address >= 0 && sector_address < ct->size) { ct->wait_adr = ct->adr + (sector_address << ct->shift); wr_func(ct->wait_adr, alg_info->cmd_secterase); } else { ct->wait_adr = ct->adr; wr_func(reg1, alg_info->cmd_chiperase); } } /* wait for queued erasing operations to finish */ static int flash29_erase_wait(void *chip_descr) { chiptype_t *ct = chip_descr; return wait_chip(ct, ct->wait_adr, ct->bus_mask); } #if HOST_FLASHING /* read the chip ID */ static uint32_t read_id(chiptype_t * ct, int adr) { unsigned int ret; uint32_t reg1 = ct->reg1; uint32_t reg2 = ct->reg2; const alg_info_t *alg_info = ct->alg_info; void (*wr_func) (uint32_t, uint32_t) = ct->wr_func; wr_func(reg1, alg_info->cmd_reset); wr_func(reg1, alg_info->cmd_unlock1); wr_func(reg2, alg_info->cmd_unlock2); wr_func(reg1, alg_info->cmd_autoselect); ret = ct->rd_func(ct->adr + (adr << ct->shift)); wr_func(reg1, alg_info->cmd_reset); return ret; } /* autodetect a 29Fxxx type chip */ static uint32_t flash29_search_chip(void *chip_descr, char *description, uint32_t pos) { chiptype_t *ct = chip_descr; int i, j, bw, cw; uint32_t m, d; uint32_t exp; const alg_info_t *alg_info; const chip_t *chip; ct->adr = pos; for (bw = 4; bw; bw >>= 1) { for (cw = bw; cw; cw >>= 1) { ct->chip_width = cw; set_chip_access(ct, bw); for (i = 0; i < NUMOF(chips); i++) { chip = &chips[i]; ct->size = chip->size * (bw / cw); ct->alg_info = alg_info = chip->alg_info; ct->reg1 = pos + ((alg_info->adr_unlock1) << (ct->shift)); ct->reg2 = pos + ((alg_info->adr_unlock2) << (ct->shift)); if ((m = read_id(ct, 0)) != chip->manufacturer) continue; exp = 0; for (j = 0; j < bw; j += cw) { exp <<= ((cw - 1) << 3); exp |= (chip->device_id) & (ct->chip_mask); } if ((d = read_id(ct, 1)) != exp) continue; /* check if we are just reading ram */ if (m == ct->rd_func(pos) && d == ct->rd_func(pos + (1 << ct->shift))) return 0; if (description) { sprintf(description, "%10s @ 0x%08lx..0x%08lx " "bw:%d cw:%d manuf:0x%02lx device:0x%04lx size:0x%08lx", chip->name, pos, pos + ct->size, bw, cw, m, d, ct->size); } return ct->size; } } } return 0; } # define CHIP_WR(base,str,field,val) \ chip_wr_long((base)+(((unsigned char*)&((str)->field)) - \ ((unsigned char*)str)), \ (val)); /* FIXME: this function assumes that data sizes and alignment requirements on target and host are identical. */ static int download_struct(void *chip_descr, uint32_t adr) { chiptype_t *ct = chip_descr; const alg_info_t *alg = ct->alg_info; uint32_t alg_adr = adr + sizeof(*ct); CHIP_WR(adr, ct, alg_info, alg_adr); CHIP_WR(adr, ct, adr, ct->adr); CHIP_WR(adr, ct, size, ct->size); CHIP_WR(adr, ct, chip_width, ct->chip_width); CHIP_WR(adr, ct, bus_width, ct->bus_width); CHIP_WR(adr, ct, reg1, ct->reg1); CHIP_WR(adr, ct, reg2, ct->reg2); CHIP_WR(alg_adr, alg, cmd_unlock1, alg->cmd_unlock1); CHIP_WR(alg_adr, alg, cmd_unlock2, alg->cmd_unlock2); CHIP_WR(alg_adr, alg, cmd_reset, alg->cmd_reset); CHIP_WR(alg_adr, alg, cmd_program, alg->cmd_program); CHIP_WR(alg_adr, alg, cmd_bypass, alg->cmd_bypass); CHIP_WR(alg_adr, alg, cmd_resbypass1, alg->cmd_resbypass1); CHIP_WR(alg_adr, alg, cmd_resbypass2, alg->cmd_resbypass2); CHIP_WR(alg_adr, alg, timeout_mask, alg->timeout_mask); return alg_adr + sizeof(*alg); } void init_flash29(int num) { register_algorithm(num, driver_magic, sizeof(chiptype_t), download_struct, flash29_search_chip, flash29_erase, 0, flash29_erase_wait, flash29_prog, prog_entry); } #else void init_flash29(int num) { register_algorithm(num, driver_magic, 0, 0, 0, flash29_erase, 0, flash29_erase_wait, flash29_prog, 0); } #endif