Files
m68k-bdm/m68k/flashlib/flash29.c
Bernd Mueller adfd70813f initial push
2026-06-17 13:44:30 +02:00

690 lines
21 KiB
C

/* $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 <stdio.h>
# include <BDMlib.h>
#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