/* $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
