static PROGMEM prog_uint32_t crc_table[16] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
    0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

unsigned long crc_update(unsigned long crc, byte data)
{
    byte tbl_idx;
    tbl_idx = crc ^ (data >> (0 * 4));
    crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);
    tbl_idx = crc ^ (data >> (1 * 4));
    crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);

    return crc;
}

class GDflashbits {
public:
  void begin(prog_uchar *s) {
    src = s;
    mask = 0x01;
  }
  byte get1(void) {
    byte r = (pgm_read_byte_near(src) & mask) != 0;
    mask <<= 1;
    if (!mask) {
      mask = 1;
      src++;
    }
    return r;
  }
  unsigned short getn(byte n) {
    unsigned short r = 0;
    while (n--) {
      r <<= 1;
      r |= get1();
    }
    return r;
  }
private:
  prog_uchar *src;
  byte mask;
};

static GDflashbits GDFB;

static byte history[264], hp;

int page, offset;

#define CS 10

#define SEL() digitalWrite(CS, LOW)
#define UNSEL() digitalWrite(CS, HIGH)

#define spix(n) SPI.transfer(n)

static void spipage(int n)
{
  spix(n >> 7);
  spix(n << 1);
  spix(0);
}

static byte status()
{
  SEL();
  spix(0xd7);   // read SPI flash status
  byte status = spix(0);
  UNSEL();
  return status;
}

static void supply(byte b)
{
  history[hp++] = b;

  if (offset == 0) {
    SEL();
    spix(0x82);
    spipage(page++);
  }
  spix(b);
  if (++offset == 264) {
    UNSEL();
    while ((status() & 0x80) == 0)
      ;
    offset = 0;
  }
}

static void GD_uncompress(PROGMEM prog_uchar *src)
{
  GDFB.begin(src);
  byte b_off = GDFB.getn(4);
  byte b_len = GDFB.getn(4);
  byte minlen = GDFB.getn(2);
  unsigned short items = GDFB.getn(16);
  hp = 0;
  offset = 0;
  while (items--) {
    if (GDFB.get1() == 0) {
      supply(GDFB.getn(8));
    } else {
      int offset = -GDFB.getn(b_off) - 1;
      int l = GDFB.getn(b_len) + minlen;
      while (l--) {
        supply(history[0xff & (hp + offset)]);
      }
    }
  }
}

static unsigned long flash_crc(int page, int n)
{
  SEL();
  SPI.transfer(0x03);
  spipage(page);

  unsigned long crc = ~0;
  unsigned long len = 264L * n;
  while (len--) {
    crc = crc_update(crc, spix(0));
  }
  crc = ~crc;
  UNSEL();
  return crc;
}

#if (CS == 2)
#include "GD.h"
static void ggd()
{
  GD.begin();
  GD.wr(IOMODE, 'F');
}
#else
static void ggd()
{
  SPI.begin();
}
#endif

static void common_setup(int part)
{
  delay(2000);
  Serial.begin(9600);
  Serial.print("Flash loader part ");
  Serial.print(part, DEC);
  Serial.println(" ... please wait...");
  ggd();

  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  delay(1);
  Serial.print("Status ");
  Serial.println(status(), HEX);
}

static void common_show_status()
{
  Serial.print("part 0: ");
  Serial.println(flash_crc(P0OFF, P0SIZE) == P0CRC ? "OK" : "--");
  Serial.print("part 1: ");
  Serial.println(flash_crc(P1OFF, P1SIZE) == P1CRC ? "OK" : "--");
  Serial.print("part 2: ");
  Serial.println(flash_crc(P2OFF, P2SIZE) == P2CRC ? "OK" : "--");
  Serial.print("part 3: ");
  Serial.println(flash_crc(P3OFF, P3SIZE) == P3CRC ? "OK" : "--");
}

static int ready(int base)
{
  return (flash_crc(base + P0OFF, P0SIZE) &&
          flash_crc(base + P1OFF, P1SIZE) &&
          flash_crc(base + P2OFF, P2SIZE));
}
