/*

  Zerocat Chipflasher --- Flash free firmware, kick the Management Engine.

  Copyright (C) 2015, 2016  kai <kmx@posteo.net>
  Copyright (C) 2016  tomás zerolo <tomas@tuxteam.de>
  Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022  Kai Mertens <kmx@posteo.net>

  This file is part of Zerocat Chipflasher.

  Zerocat Chipflasher 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 3 of the
  License, or (at your option) any later version.

  Zerocat Chipflasher 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.

  You should have received a copy of the GNU General Public License
  along with Zerocat Chipflasher.
  If not, see <http://www.gnu.org/licenses/>.


***/


/*

  Documentation
  =============


  TODO: Implement a better initial handshake with `connect`.

  TODO: Update schematic according to AN 'interfacing higher voltages'.


  Brief
  -----


  This file provides procedure `main()` of `kick`, the flasher’s 
  firmware.


***/


//  Parallax Workspace: 'Simple Libraries'
//  ======================================


# include "simpletext.h"
# include "simpletools.h"


//  Additional Parallax propeller-elf Headers
//  =========================================


# include <ctype.h>


//  Project Headers
//  ===============


# include "libcommon/common.h"
# include "libcommon/serial-codes.h"
# include "libcommon/linespec.h"
# include "libSPI/SPI-command-set.h"
# include "libSPI/SPI-flash-bits.h"
# include "libkick/proppins.h"
# include "libkick/chipspec.h"
# include "libkick/key-enable-bits.h"
# include "libkick/fast-SPI.h"
# include "kick.h"
                                              //include macro defs, type defs and func prototypes

//  Project Source Files
//  ====================


# include "libprop/putChar.c"                 //modified putChar()
# include "libcommon/hexdigit2bin.c"
# include "libcommon/linespec.c"              //line type database
# include "libkick/chipspec.c"                //chip database


//  Configuration Globals
//  =====================
//
//
//  - will be patched by propeller-load
//  - compare to: firmware/start/board.cfg


int _cfg_baudrate = -1;     //managed by host/start/Makefile
int _cfg_RST = -1;
int _cfg_terminal = -1;


//  Globals
//  =======


// SPI mode to be used through the whole program.
enum SPIMODE_t spimode = SPIMODE_3;

// Cog parameters for burn().
struct XCOG0_t xcog0;

// Cog parameters for ledstat(), initialized:
// D1=on, D2=off, D3=“Monitoring SPI-Bus-Connector”
struct XCOG1_t xcog1 = {
  -1,
  0,
  -2
};

// Pointer to chip specifications.
struct CHIPSPEC_t * pchipspec = NULL;   //no chip detected nor selected yet

// Pointer to specifications that are associated with selected line type.
struct LINESPEC_t * plinespec = linespec + LINESPECINDEX_SRECBIN;

// Global chip erase command, may be overridden by key_config().
unsigned char cmd_CE = CMD__CE_0X60;

// Global check type for end of write cycle detection.
enum CHECKTYPE_t checktype = CHECK_IS_POLLING;


//  Functions
//  =========


int main (
  void
)
{
  /*
    This is main() of `kick`, the firmware.

    Never returns.

    Notes on Boot Conditions:

    1. SPI power is disabled during boot, because the pnp MOSFET does not
       switch on with its gate pulled up and PIN_PNP being in tristate.

    2. Function ledstat() is started on another cog, and won’t be disabled
       other by switching the board off.

    3. Variable _cfg_rstdisable allows to disable the RST line for a while,
       so that the system may be started from RAM.

    4. Variable _cfg_baudrate will always be patched by `propeller-load`,
       even if not specified in the board config file. Its default value is
       115200.

    5. Variable _cfg_modeterminal allows to start a basic “propeller-terminal”
       communication instead of talking to `connect`, meant as a simple
       proof-of-concept. Btw, the Propeller exit sequence is understood
       by both: `connect` and (of course) “propeller-terminal” (set up with `propeller-load`).
  */


  //setup ledstat():
  cog_run(&ledstat, STACK_LEDSTAT);

  //disable RST
  if(!_cfg_RST) {
    high(RST_DISABLE);  //disable RST
    xcog1.orange = -1;
  }

  //reconfigure simpleterm
  if(_cfg_baudrate != BAUDRATE_DEFAULT) {
    simpleterm_reopen(PIN_RX, PIN_TX, 0, _cfg_baudrate);
  }

  //Do we talk to connect or to a “propeller-terminal”?
  if(_cfg_terminal > 0) {   //terminal mode?
    headline(1, '=', HEADER_KICK, 2);
    printi(MSG_CONNECTEDAT, _cfg_baudrate);
    if(!input(PIN_PLUGTESTn)) {
      printi(MSG_TERMINALMODE);
      menu(MODE_TERMINAL);   //call menu
    }
  }
  else {  //normal connect mode
    int k;            //used for initial handshake
    usleep(200);      //wait for `connect' to be ready
    OUTC(ENQ);        //request trigger (if not already on the way)
    k = getChar();    //receive (any) trigger

    //enable RST
    _cfg_RST = 1;
    low(RST_DISABLE);
    xcog1.orange = 0;

    if(k == ACK) {  //connect has replied
      headline(1, '=', HEADER_KICK, 2);
      printi(MSG_CONNECTEDAT, _cfg_baudrate);
      if(!input(PIN_PLUGTESTn)) {
        menu(MODE_CONNECT);    //call menu
      }
    }
  }

  //end of program
  SPI_off(SPIOFF_SOFT);
  OUTA |= BIT_PNP_DEVICE;   //disable pnp device, make sure SPI-Bus is off
  headline(0, '_', M_ENDOFPROGRAM, 2);
  printi(MSG_DETACHCLIP);
  for(int n = 3; n; n--) {
    exit_sequence(0x00);  //this is understood by both, connect _and_ “propeller-terminal”
    usleep(200000);
  }
  while(1);   //hang-up
  return(0);  //normal quit
}



unsigned char rotr8 (
  unsigned char value,
  int places
)
{
  if(places <= 0 || places >= 8)
    return value;
  else
    return value >> places | value << (8 - places);
}



unsigned char rotl8 (
  unsigned char value,
  int places
)
{
  if(places <= 0 || places >= 8)
    return value;
  else
    return value << places | value >> (8 - places);
}



unsigned char FFconfig (
  unsigned char mode_0xff
)
{
  //Step through $FF line configuration.

  int n = 0;

  //action
  mode_0xff = rotr8(mode_0xff, 5);   //rotate right six places
  if(mode_0xff & 3) {
    for(int i = 0; i < 2; i++) {
      if(mode_0xff & 1) {
        mode_0xff &= ~1;
        break;
      }
      mode_0xff = rotr8(mode_0xff, 1);               //rotate right one place
      n++;
    }
  }
  else {
    mode_0xff |= 3;
  }
  return rotl8(mode_0xff, (5 + n));   //rotate left
}



void option_writeSOTP (
  void
)
{
  //locals
  enum REPLY_t reply_usescreen;
  enum REPLY_t reply_usemainarray;
  struct LINESPEC_t * ptmp = plinespec;       //backup pointer
  unsigned char r;

  //check LDSO bit
  SPI_ini();
  r = read_register(REG_SECURITY);
  SPI_off(SPIOFF_SOFT);
  if(r & LDSO) {    //avoid ENSO if SOTP is already locked
    printi(MSG_LOCKED);
  }
  else {
    //warning
    printi(MSG_WRITETOOTP);
    printi(MSG_CHECKYOURFILE);

    //more input
    reply_usemainarray = yes_no(L2, P2_WRITEMAINARRAY, L2);

    //more input
    reply_usescreen = yes_no(L2, P2_OUTPUTONSCREEN, L2);

    //last input and action
    if(yes_no(L3, P3_SURETOPROCEED, LON)) {
      plinespec = &(linespec[LINESPECINDEX_HEXDSOTP]);
      if(reply_usemainarray == REPLY_YES)
        plinespec->type_of_payload = PAYLOAD_MAIN_ARRAY;
      report_err(rxfile(reply_usescreen << 7));             //pass mode bit
      plinespec->type_of_payload = PAYLOAD_SOTP_AREA;       //restore value
      plinespec = ptmp;                                     //restore pointer
    }
  }
}



void option_readSOTP (
  void
)
{
  //locals
  struct LINESPEC_t * ptmp = plinespec;       //backup pointer
  enum REPLY_t reply_usescreen;
  char s[SIZE_STRBUF];

  //create header string, used by S-Record and HexDump
  sprinti(
    s,
    HEADER,
    pchipspec->names,
    STR_SOTP
  );

  //more input
  reply_usescreen = yes_no(L2, P2_OUTPUTONSCREEN, L2);

  //last input and action
  if(yes_no(L3, P3_SURETOPROCEED, LON)) {
    plinespec = &(linespec[LINESPECINDEX_HEXDSOTP]);  //process Hex-Dump File only
    SPI_ini();
    cmd_ENSO();
    txfile(
      s,    //header string
      get_addrlen(ADDRLEN_IN_BITS, pchipspec->size - 1),
      0,
      SIZE_SOTP,
      reply_usescreen << 7                        //pass mode bit
    );
    cmd_EXSO();
    SPI_off(SPIOFF_SOFT);
    plinespec = ptmp;                             //restore pointer
  }
}



void option_blockbatchread (
  int blocksize
)
{
  //locals
  unsigned int last_block = pchipspec->size / blocksize - 1;
  unsigned int first_block = 0;
  int mode_screen_output;
  char s[SIZE_STRBUF];

  //create header string, used by S-Record and HexDump
  sprinti(
    s,
    HEADER,
    pchipspec->names,
    STR_ARRAY
  );

  //more input
  if(!range_input(L2, &first_block, &last_block, L2))
    return;

  //more input
  mode_screen_output = yes_no(L2, P2_OUTPUTONSCREEN, L2) << 7;

  //last input and action
  if(yes_no(L3, P3_SURETOPROCEED, LON)) {
    OUTC(INPUT_START);    //enable stdin: q for quit
    SPI_ini();
    txfile(
      s,    //header string
      get_addrlen(ADDRLEN_IN_BITS, pchipspec->size),
      first_block * blocksize,
      blocksize * (last_block - first_block + 1),
      mode_screen_output
    );
    SPI_off(SPIOFF_SOFT);
    OUTC(INPUT_STOP);     //disable stdin
  }
}



int isbindigit (
  int c
)
{
  if(c == '0' || c == '1')
    return 1;   //true
  return 0;     //false
}



unsigned int get_number (
  char * pindent,
  int (* pf) (int),
  const int digits,
  const int base
)
{
  unsigned int value = 0;
  int n = 0;
  char c;

  //start input
  OUTC(INPUT_START);      //enable stdin on host
  printi(pindent);
  do {
    c = getChar();
    if(pf(c) && (n < digits)) {
      value *= base;
      value += hexdigit2bin(c);   //valid for binary, decimal and hexadecimal
      putChar(c);
      n++;
    }
    else if(c == 8) {       //backspace
      value = 0;
      printi(pindent);
      while(n) {
        putChar(' ');
        n--;
      }
      printi(pindent);
    }
    else if(c == 13) {      //carriage return
      if(!n)
        putChar('0');
      printi(CRNL);
      break;
    }
  } while(1);
  OUTC(INPUT_STOP);     //disable stdin on host

  return value;
}



int range_input (   // return 0 upon error, -1 upon success
  enum LX_t levin,
  unsigned int * pfirst,
  unsigned int * plast,
  enum LX_t levout
)
{
  unsigned int limit = *plast + 1;

  xcog1.green = levin;

  if(*plast > *pfirst) {
    printi(P2_FIRSTBLOCK, 0, *plast);
    *pfirst = get_number(RTAB6, isxdigit, 8, 16);
    //catch out of range error
    if(*pfirst > *plast) {
      printi(MSG_OUTOFRANGE);
      return 0;
    }
  }
  if(*pfirst < *plast) {
    printi(P2_LASTBLOCK, *pfirst, *plast);
    *plast = get_number(RTAB6, isxdigit, 8, 16);
    //catch out of range error
    if( (*plast >= limit) ||
        (*plast < *pfirst)  ) {
      printi(MSG_OUTOFRANGE);
      return 0;
    }
  }

  xcog1.green = levout;
  return -1;
}



void option_blockbatcherase (
  const unsigned int blocksize,
  const unsigned char blockcmd
)
{
  /*
    Versatile block erase function, which allows erasing of multiple
    blocks of different sizes, i.e. 4K (sectors), 32K and 64K.

    SPI bus power is interrupted every n blocks, in order to avoid
    verify failure on T530 and W530 laptops.
  */


  unsigned int last_block = pchipspec->size / blocksize - 1;
  unsigned int first_block = 0;

  //info
  printi(MSG_SIZEPERBLOCK, blocksize);

  //more input
  if(!range_input(L2, &first_block, &last_block, L2))
    return;

  //check for dry-run
  check_perm();

  //last input and action
  if(yes_no(L3, P3_SURETOPROCEED, LON)) {

    SPI_ini();

    while(first_block <= last_block) {
      unsigned int firstloc;
      unsigned int r;

      printi(STR_BLOCKN, first_block);

      firstloc = first_block * blocksize;
      WPn_HIGH;
      cmd_WREN();
      CHIP_ON;
      if(blocksize == pchipspec->size)
          outbits(cmd_CE, 1 << 7);
      else
        cmd(blockcmd, firstloc, get_addrlen(ADDRLEN_IN_BITS, pchipspec->size - 1));
      CHIP_OFF;
      WPn_LOW;

      //Timeout = (blocksize >> 12) * (T_PP << 4)
      if((r = WIPcheck((blocksize >> 8) * T_PP)))
        printi(MSG_WIPCHECKS, r);
      else
        printi(MSG_TIMEOUT);

      printi(MSG_VERIFYING);
      if(verify_0xff(firstloc, blocksize)) {
        printi(MSG_FAIL);
        break;
      }
      else {
        printi(MSG_OK);
      }

      first_block++;
    }

    SPI_off(SPIOFF_SOFT);  //check_perm() has left SPI Bus in standby mode
  }
}



void option_sectorbatchprotect (
  unsigned char pswitch
)
{
  //Method to globally protect/unprotect all sectors.

  if(yes_no(L3, P3_SURETOPROCEED, LON)) {
    SPI_ini();
    WPn_HIGH;

    //any address within sector will do
    for(unsigned int addr = pchipspec->size; addr; addr -= SIZE_4K) {
      cmd_WREN();
      CHIP_ON;
      pswitch == OFF ? \
        cmd(CMD__US, addr - 1, 24) : \
        cmd(CMD__PS, addr - 1, 24);
      CHIP_OFF;
      WIPcheck(T_PP << KSL_W);   //Which one applies, page or sector WIPcheck?
    }

    WPn_LOW;
    reg_report(REG_0);
    SPI_off(SPIOFF_SOFT);
  }
}



void option_setconfig (
  void
)
{
  enum R_t ireg;
  int byte;
  int linestart;
  int n;
  int c;

  for(ireg = REG_0; ireg <= REG_SECURITY; ireg++) {
    xcog1.green = L1;

    if((ireg == REG_SECURITY)
      && !(pchipspec->cmdset & (1 << X2F_WRSCUR)))
      continue;

    if((ireg == REG_2)
      && !(pchipspec->cmdset & (1 << X11_WRSR3)))
      continue;

    if((ireg == REG_1)
      && !(pchipspec->cmdset & (1 << X31_WRSR2))
      && (pchipspec->bits[REG_1] == NULL))   //take W25Q64 into account!
      continue;

    SPI_ini();
    byte = reg_report(ireg);
    SPI_off(SPIOFF_SOFT);

    printi(
      P2_CHANGEREGISTER,
      ireg == REG_SECURITY ? STR_SECURITY : NULL,
      ireg == REG_SECURITY ? 0 : ireg
    );
    if(yes_no(L2, NULL, L2)) {
      char * p = pchipspec->bits[ireg];
      int is_writable = pchipspec->is_writable[ireg];
      int is_static = pchipspec->is_static[ireg];
      int is_otp = pchipspec->is_otp[ireg];
      char flag_w = NULL;

      printi(CRNL RTAB3 "Bit" HT "Name" HT "Flags" HT "Status" HT "Value?" CRNL);
      n = 7;
      linestart = 1;
      do {
        c = *p++;
        if(c && (c != '\t')) {
          if(linestart) {    //line start?
            linestart = 0;

            //prompt
            flag_w = NULL;
            if(is_writable & (1 << n)) {
              printi(P2_BIT);
              flag_w = 'w';
            }

            //initial tabulator + bit number
            printi(RTAB3 "%c" HT, '0' + n);
          }
          putChar(c);
        }
        else {
          //flags
          putChar('\t');
          if(is_otp & (1 << n))
            putChar('o');
          else
            putChar(flag_w);
          if(!(is_static & (1 << n)))
            putChar('v');

          //status
          printi("\t%c", byte & (1 << n) ? '1' : '0');

          //value?
          if(is_writable & (1 << n)) {
            byte &= ~(1 << n);
            if(get_number(RTAB7, isbindigit, 1, 2))
              byte |= (1 << n);
          }
          else {
            printi(HT "-" CRNL);
          }

          //new line
          linestart = 1;
          n--;
        }
      } while(c);

      printi(CRNL MSG_VALUEWOULDBE, byte);

      //warning
      if(is_otp)
        printi(MSG_WRITETOOTP);

      //action
      if(yes_no(L3, P3_SURETOPROCEED, LON)) {
        unsigned int r;

        SPI_ini();

        printi(STR_WRITING);
        WPn_HIGH;
        write_register(ireg, byte);
        WPn_LOW;
        usleep(T_RW);       //workaround: address verification error
        if((r = WIPcheck(T_PP << KSL_W)))
          printi(MSG_WIPCHECKS, r);
        else
          printi(MSG_TIMEOUT);

        printi(MSG_VERIFYING);
        if(read_register(ireg) == byte)
          printi(MSG_OK);
        else
          printi(MSG_FAIL);

        SPI_off(SPIOFF_SOFT);
      }
    }
  }
}



void key_polling (
  int * quit
)
{
  /*
    Essential function. Scans stdin for configured keys and issues the
    apropriate actions. Typing 'q' quits this routine.
  */


  char keyinput;
  int keyid;

  OUTC(INPUT_START);      //enable stdin on host
  do {
    keyinput = getChar();
    keyid = 0;
    while((keyinput != keymsg[keyid][0]) && (keyid != ID_NONE))
      keyid++;
  } while(!(keyreg & (1 << keyid)));
  OUTC(INPUT_STOP);       //disable stdin on host

  //feedback and mirror
  printi("%c" CRNL, keymsg[keyid][0]);
  printi(FMT_MIRROR, keymsg[keyid] + 3, DOTS, CRNL);
  xcog1.green = LON;    //action

  //process key input
  switch(keyinput) {
    //key active by default
    case 'q':   //quit
      {
        //action
        if(DIRA & BIT_PNP_DEVICE) {
          SPI_off(SPIOFF_FORCE);
          printi(MSG_BUSPOWEREDOFF);
        }
        else {
          *quit = 1;
          xcog1.green = LON;    //restore LED
        }
      }
      break;

    //key active by default
    case 't':   //toggle line type
      {
        //action
        if(plinespec == &(linespec[LINESPECINDEX_SRECBIN]))
          plinespec = &(linespec[LINESPECINDEX_HEXDMAIN]);
        else
          plinespec = &(linespec[LINESPECINDEX_SRECBIN]);
      }
      break;

    case 'W':   //set config
      option_setconfig();
      break;

    case 'r':   //show config
      {
        //action
        SPI_ini();
        reg_report(REG_0);
        if(pchipspec->cmdset & (1 << X35_RDSR2))
          reg_report(REG_1);
        if(pchipspec->cmdset & (1 << X15_RDSR3))
          reg_report(REG_2);
        if(pchipspec->cmdset & (1 << X2B_RDSCUR))
          reg_report(REG_SECURITY);
        SPI_off(SPIOFF_SOFT);
      }
      break;

    case 'G':   //global sector protect
      option_sectorbatchprotect(ON);
      break;

    case 'X':   //global sector unprotect
      option_sectorbatchprotect(OFF);
      break;

    //key active by default
    case 'a':   //previous record
      {
        //action
        if(pchipspec == NULL)
          pchipspec = chipspec;
        else if(pchipspec == chipspec)
          pchipspec = chipspec + CHIPSPEC_ARRAYSIZE - 1;
        else
          pchipspec--;
      }
      break;

    //key active by default
    case 'f':   //next record
      {
        //action
        if(pchipspec == NULL)
          pchipspec = chipspec;
        else if(pchipspec == chipspec + CHIPSPEC_ARRAYSIZE - 1)
          pchipspec = chipspec;
        else
          pchipspec++;
      }
      break;

    //key active by default
    case 'd':   //detect chip
      {
        unsigned char powerup_extra = POWERUP_EXTRA;
        int id_JEDEC;

        //power up
        do {
          SPI_ini();
          //JEDEC__RDID (should be the first command to use)
          if(!(id_JEDEC = cmd_RDID_JEDEC()))
            SPI_off(SPIOFF_FORCE);
          else
            break;
        } while(powerup_extra--);

        //set chip
        if(powerup_extra == 0xff) {
          printi(MSG_CLIPATTACHED);
        }
        else {
          struct CHIPSPEC_t * pcheck = chipspec;

          while(pcheck < chipspec + CHIPSPEC_ARRAYSIZE) {
            if((pcheck->cmdset & (1 << X9F_RDID)) && id_JEDEC == pcheck->id_JEDEC)
              break;
            pcheck++;
          }
          if(pcheck == chipspec + CHIPSPEC_ARRAYSIZE) {   //no match occured
            printi(MSG_NOMATCHINDB);
            printi(MSG_JEDECID, id_JEDEC);
            SPI_off(SPIOFF_FORCE);
          }
          else {
            pchipspec = pcheck;
            printi(
              MSG_CHIPDETECTED,
              pchipspec->id_JEDEC
            );
            if(!pchipspec->is_static)
              printi(MSG_BUSKEPTPOWERED);
            if(pchipspec->id_JEDEC == ID_JEDEC_W25X40)    //What about ID_JEDEC_W25X64?
              printi(MSG_ENABLEWPCONTROL);
            SPI_off(SPIOFF_SOFT);
          }
        }
      }
      break;

    case 'p':   //read page
      option_blockbatchread(SIZE_256);
      break;

    case 'n':   //read random range
      option_blockbatchread(1);
      break;

    case 's':   //read block 4k
      option_blockbatchread(SIZE_4K);
      break;

    case 'o':   //read 64bytes SOTP area (512bits on MX Chips)
      option_readSOTP();
      break;

    case 'k':   //read block 32k
      option_blockbatchread(SIZE_32K);
      break;

    case 'b':   //read block 64k
      option_blockbatchread(SIZE_64K);
      break;

    case 'c':   //read chip
      option_blockbatchread(pchipspec->size);
      break;

    case 'O':   //SOTP file -> SOTP area
      option_writeSOTP();
      break;

    case 'S':   //4k block erase
      option_blockbatcherase(SIZE_4K, CMD__SE);
      break;

    case 'K':   //erase block 32k
      option_blockbatcherase(SIZE_32K, CMD__BE32K);
      break;

    case 'B':   //erase block 64k
      option_blockbatcherase(SIZE_64K, CMD__BE64K);
      break;

    case 'E':   //erase chip
      option_blockbatcherase(pchipspec->size, cmd_CE);
      break;

    case 'I':   //write file to chip
      {
        //screen output?
        unsigned char screen_output = yes_no(L2, P2_OUTPUTONSCREEN, L2);

        //check for dry-run
        check_perm();

        //action
        if(yes_no(L3, P3_SURETOPROCEED, LON)) {
          OUTC(INPUT_START);      //enable stdin on host, e.g.: q for quit
          report_err(rxfile(screen_output));
          OUTC(INPUT_STOP);       //disable stdin on host
        }
      }
      break;

    case '$':   //increase payload
      {
        //action
        switch (plinespec->payload) {
          case MAX_PAYLOAD_SREC:
            plinespec->payload = BASE_PAYLOAD;
            break;
          case 128:
            plinespec->payload = MAX_PAYLOAD_SREC;
            break;
          default:
            plinespec->payload <<= 1;
            break;
        }
      }
      break;

    case '%':   //cycle $FF modes
      {
        //action
        plinespec->mode_0xff = FFconfig(plinespec->mode_0xff);
      }
      break;

    case '~':   //toggle WIP check method
      {
        //action
        checktype ^= 1;
      }
      break;

    case '?':   //show menu
    default:
      {
        //action
        menu_help();
      }
      break;
  }
  printi(FMT_MIRROR, DOTS, keymsg[keyid] + 3, CRNL);    //mirror
}



void check_perm (
  void
)
{
  /*
    Checks whether a write fail should be expected. A function with very
    basic and poor functionality.

    Shall we ignore bit 7 of **all** chips for the test?
  */


  unsigned char r;

  SPI_ini();
  r = read_register(REG_0);
  SPI_off(SPIOFF_SOFT);
  
  r &= 0b00111100;    //check common block protection bits only
  r &= pchipspec->is_writable[REG_0];

  if(r)
    printi(MSG_MEMORYPROTECTED);
}



void menu (
  int mode
)
{
  //Put the chipflasher menu on screen and invoke key_polling().

  int quit = 0;
  int n;

  while(!quit) {
    //set key
    keyreg = KEYREG_DEFAULT;
    if(mode & MODE_CONNECT)
      key_config();

    //greeting
    headline(0, '_', M_CONFIGSTATUS, 2);

    //status line 1
    printi(
      STATLINE_1,
      plinespec->linetype_name,
      plinespec->payload,
      plinespec->mode_0xff & MODE_SPLIT ? STR_MODE_SPLIT : NULL,
      plinespec->mode_0xff & MODE_STRIP ? STR_MODE_STRIP : NULL
    );

    //status line 2
    if(pchipspec != NULL) {
      printi(
        STATLINE_2,
        pchipspec - chipspec,
        pchipspec->id_JEDEC,
        pchipspec->size,
        pchipspec->names
      );
    }

    //status line 3
    printi(
      STATLINE_3,
      input(PIN_PLUGTESTn) ? "not " : NULL,
      (DIRA & BIT_PNP) ? NULL : "not ",
      checktype == CHECK_IS_POLLING ? STR_POLLING : STR_TIMEOUT
    );

    //user input
    headline(0, '_', M_YOURINPUT, 2);
    printi(P1_START);
    for(n = 0; n < ID_NONE - 1; n++)
      if(keyreg & (1 << n))
        printi("%c", keymsg[n][0]);
    if(keyreg & (1 << n))
      putChar(keymsg[n][0]);    //last key without separator
    printi(P1_STOP);

    //key polling
    xcog1.green = L1;
    key_polling(&quit);
    xcog1.green = LOFF;
  }
}



void menu_help (
  void
)
{
  //menu
  //  change configuration
  headline(0, '_', M_CHANGECONFIG, 2);
  menuline(   KEYID_a,      KEYID_t,      KEYID_pcent   );
  menuline(   KEYID_f,      KEYID_dollar,   KEYID_tilde   );

  //  detect/report/read
  headline(0, '_', M_CHIPREAD, 2);
  menuline(   KEYID_d,      KEYID_p,      KEYID_s       );
  menuline(   KEYID_r,      KEYID_o,      KEYID_k       );
  menuline(   KEYID_n,      KEYID_c,      KEYID_b       );

  //  set/erase/write
  headline(0, '_', M_CHIPWRITE, 2);
  menuline(   KEYID_W,      KEYID_E,      KEYID_S       );
  menuline(   KEYID_G,      KEYID_O,      KEYID_K       );
  menuline(   KEYID_X,      KEYID_I,      KEYID_B       );

  //  cancel/quit
  headline(0, '_', M_PROGRAMCONTROL, 2);
  menuline(   KEYID_q,      ID_NONE,      KEYID_qmark   );
}



void headline (
  int preceding_nl,
  char c,
  char * pstr,
  int trailing_nl
)
{
  /*
    Put a headline on screen.

    Print a new line newlines times,
    then print a line of character c, embed string at pstr
  */


  hr(preceding_nl, c, NULL, 1);
  hr(0, ' ', pstr, trailing_nl);
}



void hr (
  int preceding_nl,
  char c,
  char * pstr,
  int trailing_nl
)
{
  //Put a horizontal line on screen.

  int m = MENUWIDTH - strlen(pstr);

  while(preceding_nl--)
    printi(CRNL);
  while(m--)
    putChar(c);
  printi(pstr);
  while(trailing_nl--)
    printi(CRNL);
}



int get_addrlen (
  enum TYPEOFADDRLEN_t type_of_addrlen,
  unsigned int addr
)
{
  /*
    Auxiliary function that returns the length of an address for
    different situations.
  */


  unsigned int mask = 0xffffff00;
  int bytes = 1;

  while(addr & mask) {
    bytes++;
    mask <<= 8;
  };

  if(type_of_addrlen == ADDRLEN_SREC)
    if(bytes == 1)
      return 2;

  if(type_of_addrlen == ADDRLEN_IN_BITS)
    return(bytes << 3);

  return(bytes);
}



void key_config (
  void
)
{
  //Enable menu keys according to register flags.

  unsigned int reg = keyreg;

  if(plinespec->linetype == LINETYPE_SREC) {
    reg |= KEYBIT_dollar | KEYBIT_pcent;
  }

  if(pchipspec->cmdset & (1 << X03_READ)) {
    //enable 'fetch file and write chip'
    if((pchipspec->cmdset & (1 << X02_PP)) ||
        (pchipspec->cmdset & (1 << X02_BP)) ||
        (pchipspec->cmdset & (1 << XAD_CP)))
      reg |= KEYBIT_I;

    //enable 'read page'
    if(pchipspec->cmdset & (1 << X02_PP))
      reg |= KEYBIT_p;

    //enable 'sector erase'
    if(pchipspec->cmdset & (1 << X20_SE))
      reg |= KEYBIT_s | KEYBIT_S;

    //read block 32k
    //erase block 32k
    if(pchipspec->cmdset & (1 << X52_BE32K))
      reg |= KEYBIT_k | KEYBIT_K;

    //read block 64k
    //erase block 64k
    if(pchipspec->cmdset & (1 << XD8_BE64K))
      reg |= KEYBIT_b | KEYBIT_B;

    reg |= KEYBIT_c | KEYBIT_n;

    //enable 'erase chip'
    if(pchipspec->cmdset & (1 << X60_CE)) {
      reg |= KEYBIT_E;
    }
    else if(pchipspec->cmdset & (1 << XC7_CE)) {
      reg |= KEYBIT_E;
      cmd_CE = CMD__CE_0XC7;  //override global CE command
    }

    //enable 'report status register'
    if(pchipspec->cmdset & (1 << X05_RDSR))
      reg |= KEYBIT_r;

    //enable 'write block protection bits'
    if(pchipspec->cmdset & (1 << X01_WRSR))
      reg |= KEYBIT_W;

    //enable 'global protect'
    if(pchipspec->cmdset & (1 << X36_PS))
      reg |= KEYBIT_G;

    //enable 'global unprotect'
    if(pchipspec->cmdset & (1 << X39_US))
      reg |= KEYBIT_X;

    //enable 'read SOTP', 'write SOTP'
    if(pchipspec->cmdset & (1 << XB1_ENSO))
      reg |= KEYBIT_o | KEYBIT_O;
  }

  keyreg = reg;
}



void menuline (
  int kid1,
  int kid2,
  int kid3
)
{
  //Put a menu line with selected menu options on terminal screen.

  int kbit1 = 1 << kid1;
  int kbit2 = 1 << kid2;
  int kbit3 = 1 << kid3;

  if(keyreg & (kbit1 | kbit2 | kbit3)) {
    printi(
      "%s"
      RTAB4"%s"
      RTAB8"%s"
      CRNL,
      keyreg & kbit1 ? keymsg[kid1] : NULL,
      keyreg & kbit2 ? keymsg[kid2] : NULL,
      keyreg & kbit3 ? keymsg[kid3] : NULL
    );
  }
}



unsigned char verify_0xff (
  unsigned int firstloc,
  unsigned int memsize
)
{
  /*
    This function scans the specified chip memory for values different
    than 0xff. If a value different to 0xff is found, an error status is
    returned.
  */


  unsigned char err = 0;

  CHIP_ON;
  cmd(CMD__READ, firstloc, get_addrlen(ADDRLEN_IN_BITS, pchipspec->size - 1));
  while(memsize--) {
    if(inb() != 0xff) {
      err = 1;
      break;
    }
  }
  CHIP_OFF;
  return err;
}



void mkline_SREC (
  struct LINEDATA_t * plinedata,
  unsigned char typeS
)
{
  /*
    This function wraps a binary payload into a Motorola S-record frame.

    The result including line ending characters is stored in a buffer.
    The frame size (without control chars like STX/ETX) depends on the
    used address lenght, it is...

     - 12 chars for S1..S9,
     - 14 chars for S2..S8,
     - 16 chars for S3..S7 data records.

    Inline 0xff blobs of that size (or less) should not be filtered in
    order to prevent additional transmission overhead when reading chip
    content.
  */


  int checksum;
  int v;
  int n;
  int i = plinedata->rd;

  //print Sn... (2 bytes)
  printi("S%01d", typeS - '0');

  //print length (2 bytes)
  v = S_addrlen(typeS) + plinedata->payload + 1;
  printi("%02x", v);
  checksum = v;  //start summing checksum

  //print address (4..8 bytes)
  n = S_addrlen(typeS);
  while(n--) {
    v = (plinedata->startaddr >> (n << 3)) & 0xff;
    printi("%02x", v);
    checksum += v;
  };

  //print data (n bytes)
  n = plinedata->payload;
  while(n--) {
    char byte = plinedata->buf[i++ & plinedata->mask];
    putChar(byte);
    checksum += byte;
  };

  //print checksum (2 bytes) and CR+NL (2 bytes)
  printi("%02x\r\n", ~checksum & 0xff);
}



void headline_SREC (
  char * pheader
)
{
  /*
    This function invokes mkline_SREC(), but initializes parameters first
    with apropriate header data.
  */


  struct LINEDATA_t linedata = {
    pheader,                //buf: characters are stored in header
    0xff,                   //mask: allow 0..255 characters
    0,                      //rd: reset to zero
    0,                      //wr: reset to zero
    0x0000,                 //startaddr: 0x0000
    strlen(pheader)         //payload: length of header string
  };

  mkline_SREC(
    &linedata,
    '0'
  );
}



void dataline_SREC (
  struct LINEDATA_t * plinedata
)
{
  mkline_SREC(
    plinedata,
    '0' + get_addrlen(ADDRLEN_SREC, plinedata->startaddr) - 1
  );
}



void summaryline_SREC (
  unsigned int lines
  )
{
  //Write a Motorola Summary Record, Type 6 or 5.

  struct LINEDATA_t r = { NULL, 0, 0, 0, lines, 0 };

  mkline_SREC(
    &r,
    get_addrlen(ADDRLEN_SREC, lines) > 2 ? '6' : '5'
  );
}



void execstartline_SREC (
  unsigned int final_addr
)
{
  //Write a Motorola ExecStart Record, Type 9, 8, or 7.

  const unsigned int execstart = 0;
  struct LINEDATA_t r = { NULL, 0, 0, 0, execstart, 0 };

  mkline_SREC(
    &r,
    '0' + 11 - get_addrlen(ADDRLEN_SREC, final_addr)
  );
}



enum REPLY_t yes_no (
  enum LX_t levin,
  char * question,
  enum LX_t levout
)
{
  /*
    This function allows to put a question which expects true or false.

    If it is used as a last security check before some delicate action
    takes place, level_rst should be zero in order to flag that no more
    inquiry will follow.
  */


  unsigned char k;

  xcog1.green = levin;

  printi(question);

  OUTC(INPUT_START);    //enable stdin on host
  do
    k = getChar();
  while(k != 'Y' && k != 'n');
  OUTC(INPUT_STOP);     //disable stdin on host

  printi("%c" CRNL, k);

  if(k == 'Y') {
    xcog1.green = levout;
    return REPLY_YES;
  }
  return REPLY_NO;
}



unsigned char adjust (
  unsigned char payload,
  unsigned int addr
)
{
  //adjust payload?
  if((payload == MAX_PAYLOAD_SREC) && (addr & 0xffff0000))
    payload--;
  if((payload == MAX_PAYLOAD_SREC - 1) && (addr & 0xff000000))
    payload--;

  return payload;
}



int wrap_dataline (
  void (* pf) (struct LINEDATA_t *),
  struct LINEDATA_t * plinedata
)
{
  char c;

  do {
    OUTC(STX);
    pf(plinedata);
    OUTC(ETX);
    c = getChar();
  } while(c == ENQ);
  if(c != ACK)
    return -1;

  return 0;
}



void DUMMY_data (
  struct LINEDATA_t * plinedata
)
{
  //this function does noting, intentionally
}



signed char chip_read (
  unsigned int firstloc,
  unsigned int memsize,
  unsigned int * lines
)
{
  //switches:
  #define SCANNING_DATA         0x00    //test != 0xff, test_old != 0xff
  #define DATA_START            0x01    //test != 0xff, test_old == 0xff
  #define FF_START              0x02    //test == 0xff, test_old != 0xff
  #define SCANNING_FF           0x03    //test == 0xff, test_old == 0xff
  //flags:
  #define APPLY_CUT             0x01
  #define HAS_NOT_BEEN_CUT_YET  0x02
  #define IS_0XFF               0x04
  //aux:
  #define MODE_0XFF             plinespec->mode_0xff
  #define INLINE_0XFF           (MODE_0XFF & MODE_INLINE)

  // SIZE_256 would allow to hold max. PAYLOAD_SREC (252 Bytes)
  // while being power-of-two, thus facilitating index mask control.
  #define DATABUF               SIZE_256
  #define MASK_DATABUF          DATABUF - 1

  unsigned char payload = adjust(plinespec->payload, firstloc);
  unsigned char chars_tested = 0;
  unsigned char cut = 0;
  unsigned char test_old;

  /*
   * Create special startup condition:
   * We are jumping directly into DATA_START or SCANNING_FF.
   */
  unsigned char test = 0xff;                //makes it easy to check for 0xff
  unsigned char runlen = INLINE_0XFF + 1;   //SCANNING_FF decrements runlen
  unsigned char flags = ((IS_0XFF | HAS_NOT_BEEN_CUT_YET) & ~APPLY_CUT);

  void (* pfdataline) (struct LINEDATA_t *);
  void (* pfdataline_dummy) (struct LINEDATA_t *);

  //shift left: provide extra space for inline prefetch
  char databuf[DATABUF << 1];
  struct LINEDATA_t linedata = {
    databuf,
    MASK_DATABUF,
    0,
    0,
    firstloc,
    0
  };

  //assign function pointers
  switch(plinespec->linetype) {
    case LINETYPE_HEXD:   pfdataline = dataline_HEXD;       break;
    default:              pfdataline = dataline_SREC;       break;
  }
  pfdataline_dummy = DUMMY_data;

  //scan specified memory
  while(memsize) {
    //save & prefetch
    test_old = test;            //save
    test = inb();               //prefetch in respect to chars_tested

    //store prefetch in buffer
    linedata.buf[linedata.wr++ & linedata.mask] = test;

    //check memsize first to catch address roll-over
    if(chars_tested == memsize) {
      cut = chars_tested;
      flags |= APPLY_CUT;
    }
    else if(MODE_0XFF & MODE_SPLIT) {
      switch((((test == 0xff) << 1) | (test_old == 0xff))) {
        case FF_START:
          cut = chars_tested;     //save cut position in case we need later
          flags |= HAS_NOT_BEEN_CUT_YET;
          runlen = INLINE_0XFF;
          if(!runlen) {
            if(cut)
              flags |= APPLY_CUT;
          }
          break;

        case SCANNING_FF:
          if(runlen) {
            runlen--;
          }
          else {
            if(flags & HAS_NOT_BEEN_CUT_YET) {
              flags &= ~HAS_NOT_BEEN_CUT_YET;
              if(cut)
                flags |= APPLY_CUT;
            }
          }
          break;

        case DATA_START:
          if(!runlen) {
            cut = chars_tested;     //save cut position in case we need later
            if(cut)
              flags |= APPLY_CUT;
          }
          else {
            cut = 0;    //we didn’t use the saved cut position, let’s reset to zero
          }
          break;

        case SCANNING_DATA:
          break;
      }
    }

    //payload?
    if(chars_tested == payload) {
      if(!(flags & APPLY_CUT)) {
        flags |= APPLY_CUT;
        if(!cut)
          cut = chars_tested;
      }
    }

    //check for 0xff line
    if(test_old != 0xff)
      flags &= ~IS_0XFF;

    //evaluate flags
    if(flags & APPLY_CUT) {
      //update
      chars_tested -= cut;
      memsize -= cut;

      //skip output of 0xff?
      if((MODE_0XFF & MODE_STRIP) && (flags & IS_0XFF)) {
        //make empty data line, keep STX/ETX/CAN protocol running
        if(wrap_dataline(pfdataline_dummy, NULL))
          return -1;
      }
      else {
        //update linedata.payload
        linedata.payload = cut;

        //sum up lines
        *lines += 1;

        //send data line
        if(wrap_dataline(pfdataline, &linedata))
          return -1;
      }

      //update
      linedata.rd += cut;
      linedata.startaddr += cut;

      //adjust payload
      payload = adjust(payload, linedata.startaddr);

      //reset values
      flags = (IS_0XFF & ~APPLY_CUT & ~HAS_NOT_BEEN_CUT_YET);
      cut = 0;
    }

    //increment counter
    chars_tested++;
  }
  return 0;
}



int wrap_headline (
  void (* pf) (char * pheader),
  char * pheader
)
{
  char c;

  do {
    OUTC(STX);
    pf(pheader);
    OUTC(ETX);
    c = getChar();
  } while(c == ENQ);
  if(c != ACK)
    return -1;

  return 0;
}



int wrap_summaryline (
  void (* pf) (unsigned int),
  unsigned int lines
)
{
  char c;

  do {
    OUTC(STX);
    pf(lines);
    OUTC(ETX);
    c = getChar();
  } while(c == ENQ);
  if(c != ACK)
    return -1;

  return 0;
}



void txfile (
  char * pheader,
  unsigned char addrlen_in_bits,
  unsigned int firstloc,
  unsigned int memsize,
  unsigned char screenmode
)
{
  unsigned int lines = 0;

  //activate host
  OUTC(SOH);
  if(screenmode & MODE_SCREEN_OUTPUT)
    OUTC(CHIP_TO_FILE);
  else
    OUTC(CHIP_TO_FILE_NOSCREEN);
  OUTC(plinespec->linetype);

  //put chip on
  CHIP_ON;
  cmd(CMD__READ, firstloc, addrlen_in_bits);

  //select line type
  switch(plinespec->linetype) {
    case LINETYPE_SREC:
      //make & put header
      if(wrap_headline(headline_SREC, pheader))
        goto ERROR;

      //read chip
      if(chip_read(firstloc, memsize, &lines))
        goto ERROR;

      //make & put summary line
      if(wrap_summaryline(summaryline_SREC, lines))
        goto ERROR;

      //make & put termination line
      if(wrap_summaryline(execstartline_SREC, firstloc + memsize - 1))
        goto ERROR;

      break;

    case LINETYPE_HEXD:
      //make & put header
      if(wrap_headline(headline_HEXD, pheader))
        goto ERROR;

      //read chip
      if(chip_read(firstloc, memsize, &lines))
        goto ERROR;

      //make & put summary line
      if(wrap_summaryline(summaryline_HEXD, lines))
        goto ERROR;

      break;
  }
  goto SUCCESS;

ERROR:

SUCCESS:
  CHIP_OFF;

  //terminate connection
  OUTC(EOT);
  return;
}



void headline_HEXD (
  char * pheader
)
{
  printi(FMT_HEADHEXD, pheader);
}



void summaryline_HEXD (
  unsigned int lines
)
{
  printi(FMT_TAILHEXD, lines);
}



# ifdef OUTC_DEBUG
void putCharLE (
  char c
)
{
  if(c == CARR_RET)
    c = CLIM_CR;
  else if(c == NEW_LINE)
    c = CLIM_NL;
  putChar(c);
}
# endif



void dataline_HEXD (
  struct LINEDATA_t * plinedata
)
{
  /*
    This function transforms a binary payload into a row of ascii chars
    including line ending characters and stores it in a buffer.

    The first buffer location holds the byte length of the whole line.
    Each payload byte is represented by 4 chars.
    Additional frame data adds 14 chars.

    As this function should generate a dump, filtering 0xff bytes should
    probably be switched off when reading chip content.
  */


  int i1, i2, n1, n2, x1, x2;

  //set counters
  i1 = i2 = plinedata->rd;
  n1 = n2 = plinedata->payload;
  x1 = x2 = plinespec->payload - n1;

  //write addr
  printi("%08x: ", plinedata->startaddr);

  //write hex bytes
  while(n1--)
    printi("%02x ", plinedata->buf[i1++ & plinedata->mask]);

  //fill with white space
  while(x1--)
    printi("   ");

  //append ascii separator
  printi(" #");

  //append ascii equivalent
  while(n2--) {
    unsigned char test;
    test = plinedata->buf[i2++ & plinedata->mask] & 127;    //ASCII: 7bit only
    putChar(isprint(test) ? test : '.');
  }
  while(x2--)
    putChar(' ');

  //write new line
  printi(CRNL);

}



void cmd_ENSO (
  void
)
{
  CHIP_ON;
  outbits(CMD__ENSO, 1 << 7);
  CHIP_OFF;
}



void cmd_EXSO (
  void
)
{
  CHIP_ON;
  outbits(CMD__EXSO, 1 << 7);
  CHIP_OFF;
}



void cmd_WREN (
  void
)
{
  //Set Write Enable Latch bit.

  CHIP_ON;
  outbits(CMD__WREN, 1 << 7);
  CHIP_OFF;
}



void enable_register_write (
  void
)
{
  //Set Status Register Write Enable Latch bit.

  CHIP_ON;
  if(pchipspec->cmdset & (1 << X50_EWSR))
    outbits(CMD__EWSR, 1 << 7);
  else
    outbits(CMD__WREN, 1 << 7);
  CHIP_OFF;
}



void write_register (
  const enum R_t ireg,
  const unsigned char byte
)
{
  //Write register.

  unsigned char msbit = 7;
  unsigned int value = 0 | byte;

  if(ireg == REG_1) {
    //W25Q64:
    //  Get SR0 backup.
    //  Use X01_WRSR to access SR1.
    if(pchipspec->id_JEDEC == ID_JEDEC_W25Q64FV) {
      value |= (read_register(REG_0) & pchipspec->is_writable[REG_0]) << 8;
      msbit = 15;
    }
  }

  if(ireg != REG_SECURITY) {
    //enable register write access
    enable_register_write();
  }

  CHIP_ON;
  switch(ireg) {
    case REG_SECURITY:
      //FIXME: How to use WRSCUR command exactly?
      outbits(CMD__WRSCUR, 1 << 7);
      break;

    case REG_2:
      outbits(CMD__WRSR3, 1 << 7);
      break;

    case REG_1:
      if(msbit == 15) {
        // FIXME: Are we going to write static bits with values from volatile bits?
        outbits(CMD__WRSR, 1 << 7);
      }
      else {
        outbits(CMD__WRSR2, 1 << 7);
      }
      break;

    default:
      outbits(CMD__WRSR, 1 << 7);
      break;
  }
  outbits(value, 1 << msbit);
  CHIP_OFF;
}



void exit_sequence (
  const char status
)
{
  /*
    Tell terminal to exit listening mode.
    status  A status byte could be passed as well.
  */
  

  OUTC(EXIT_CHAR_A);
  OUTC(EXIT_CHAR_B);
  OUTC(status);  //status
}



unsigned int WIPcheck (
  unsigned int limit
)
{
  /*
    Determine end of write cycle by polling the WIP bit.

    Return -1 upon overflow.

    When flashing the bottom chip#1 of a T530, no proper WIP feedback is
    provided! However, erase and write still is achievable if we use a
    simple timeout function, with reasonable values.
  */


  if(checktype == CHECK_IS_POLLING) {   //test global checktype
    unsigned int n = 0;

    //turn timeout into reasonable limit value
    limit >>= KSR_T;

    //continously read status register and poll WIP bit
    CHIP_ON;
    outbits(CMD__RDSR, 1 << 7);
    while(n++ < limit) {
      if(!(inb() & WIP)) {
        CHIP_OFF;
        return n;
      }
    }
    CHIP_OFF;
    //n == limit
  }
  else {
    unsigned int usec_limit = 0;
    unsigned int sec_limit = 0;

    usec_limit = limit;
    if(usec_limit > 1000000) {
      sec_limit = usec_limit / 1000000;
      usec_limit = usec_limit % 1000000;
      sleep(sec_limit);
    }
    usleep(usec_limit);
  }
  return 0;
}



unsigned int CPM_polling (
  void
)
{
  //Poll CPM bit.

  unsigned char b;
  unsigned int n = 0;

  CHIP_ON;
  outbits(CMD__RDSR, 1 << 7);
  do
  {
    b = inb();
    n++;
  }
  while((b & CPM) && n);  //cancel on overflow
  CHIP_OFF;
  return n;
}



unsigned char reg_report (
  enum R_t ireg
)
{
  //Put status register's content on screen.

  char * pbits = pchipspec->bits[ireg];
  int is_writable = pchipspec->is_writable[ireg];
  int is_static = pchipspec->is_static[ireg];
  int is_otp = pchipspec->is_otp[ireg];
  int status;

  //Register x: bit, name, flags, status
  printi(
    CRNL
    "%sRegister %c:"                          CRNL
    "Info\t\to=otp\tw=writable\tv=volatile"   CRNL
    "Bit\t\t7\t6\t5\t4\t3\t2\t1\t0"           CRNL
    "Name\t\t%s"                              CRNL,
    ireg == REG_SECURITY ? STR_SECURITY : "",
    ireg == REG_SECURITY ? '0' : '0' + ireg,
    pbits
  );

  //flags (otp or writable? volatile?)
  printi("Flags\t\t");
  for(int n = (1 << 7); n; n >>= 1) {
    printi(
      "%s%s\t",
      (is_otp & n) ? "o" : (is_writable & n) ? "w" : NULL,
      (is_static & n) ? NULL : "v"
    );
  }
  printi(CRNL);

  //status
  status = read_register(ireg);
  printi("Status\t\t");
  for(int n = (1 << 7); n; n >>= 1)
    printi("%d\t", (status & n) ? 1 : 0);
  printi("\r\n\n");

  return status;
}



inline unsigned char inb (
  void
)
{
  return inbits(1 << 7);
}



unsigned int inbits (
  unsigned int msbit          //must not be zero!
)
{
  unsigned int in = 0;

  do {
    CLOCK_LOW; tCLQV;
    if(INA & BIT_MISO)
      in |= msbit;
    CLOCK_HIGH;
  } while((msbit >>= 1));

  return in;
}



void outbits (
  const unsigned int value,
  unsigned int msbit          //must not be zero!
)
{
  do {
    if(value & msbit) {
      CLOCK_LOW;
      SO_HIGH;
      CLOCK_HIGH;
      continue;
    }
    CLOCK_LOW;
    SO_LOW;
    CLOCK_HIGH;
  } while((msbit >>= 1));
}



void cmd (
  const unsigned char cmd,
  const unsigned int value,
  unsigned char bits
)
{
  outbits(cmd, 1 << 7);
  outbits(value, 1 << --bits);
}



int cmd_RDID_JEDEC (
  void
)
{
  int id_JEDEC;
  CHIP_ON;
  outbits(CMD__RDID, 1 << 7);
  id_JEDEC = inbits(1 << 23);
  if(pchipspec->cmdset & (1 << X00_NOP))   //for SST25VF016B only?
    outbits(CMD__NOP, 1 << 7);
  CHIP_OFF;
  return id_JEDEC;
}



void cmd_DP (
  void
)
{
  int dt = (CLKFREQ / 1000000) * 10;

  CHIP_ON;
  outbits(CMD__DP, 1 << 7);
  CHIP_OFF;
  //Macronix Datasheet:
  //now wait about 10µs for tDP(2)
  waitcnt(CNT + dt);
}



unsigned char read_register (
    enum R_t ireg
)
{
  /*
    Read a chip register.

    regno   Number of the register to be read.
    Value `0` attempts to read the standard Status Register.
    Value `3` attempts to read the Security Register.
    Returns the content of the register.
  */


  unsigned char buf;

  CHIP_ON;
  switch(ireg) {
    case REG_SECURITY:
      outbits(CMD__RDSCUR, 1 << 7);
      break;

    case REG_2:
      outbits(CMD__RDSR3, 1 << 7);
      break;

    case REG_1:
      outbits(CMD__RDSR2, 1 << 7);
      break;

    case REG_0:
    default:
      outbits(CMD__RDSR, 1 << 7);
      break;
  }
  buf = inb();
  CHIP_OFF;

  return buf;
}



void cmd_WRDI (
  void
)
{
  CHIP_ON;
  outbits(CMD__WRDI, 1 << 7);
  CHIP_OFF;
}



void SPI_ini (
  void
)
{
  /*
    Initialize SPI bus; activate hardware write protection.

    Initialize Propeller pins attached to SPI lines:

    - Activate pnp mosfet and power chip on.
    - Activate hardware write protection.
    - PIN_MISO is always tristate, for it is used as input.
    - Setup clock level according to spimode.
    - Activate selected clock pins.
  */


  //entry condition: all outputs are tristate.

  //adjust pins
  WPn_LOW;
  HOLDn_HIGH;
  SO_LOW;

  //prepare all clock pin output registers
  if(spimode == SPIMODE_0)
    OUTA &= ~SCLK_AVAIL;
  else
    OUTA |= SCLK_AVAIL;

  //apply SPI power and let it come up together with PIN_CEn
  if(!(DIRA & BIT_PNP_DEVICE)) {
    DIRA |= BIT_PNP_DEVICE;         //enable pnp device
    usleep(POWERUP_SPI);            //used for chips within motherboards
  }

  //activate output of selected pins
  DIRA |= (SCLK_ACTIVE | BIT_WPn | BIT_HOLDn | BIT_MOSI);

  //give lines some time to settle
  //Note this is essential for the GA-G41M-ES2L!
  usleep(POWERUP_SPILINES);
}



void SPI_off (
  enum SPIOFF_t spioff
)
{
  /*
    Switch SPI bus off.

    To enhance security, deep power down is entered right before switching
    off SPI power. When the SPI chip is powered on later with SPI_ini(),
    it will not start in deep power down mode, but in normal standby mode.

    Leave Propeller pins in tristate condition, they might be used by other cogs!
  */


  int is_static = 0;

  for(int n = REG_0; n <= REG_SECURITY; n++)
    is_static |= pchipspec->is_static[n];

  switch(spioff) {
    case SPIOFF_FORCE:
      is_static = 1;

    case SPIOFF_SOFT:
      if(is_static && (pchipspec->cmdset & (1 << XB9_DP)))
        cmd_DP();   //enter deep power down as a security feature
      DIRA &= ~SPI_BUS_AVAIL;   //pins go tristate

    case SPIOFF_POWER:
      //This case must not be used without a preceding SPIOFF_SOFT!
      if(is_static)
        DIRA &= ~BIT_PNP_DEVICE;    //disable pnp device
      break;
  }
  usleep(POWERUP_OFFTIME);    //separate consecutive SPI Power-ups
}



int hex2bin (
  unsigned char a,
  unsigned char b
)
{
  /*
    Converts a pair of hexadecimal chars into binary value.
    a   First char (i.e. msnibble) of the character pair.
    b   Second char (i.e. lsnibble) of the character pair.
        Returns binary value 0x00 to 0xff or -1 if no validation possible.
  */


  signed char hnib;
  signed char lnib;

  hnib = hexdigit2bin(a);
  lnib = hexdigit2bin(b);

  if(hnib == -1 || lnib == -1)
    return -1;  //no true hex
  return (hnib << 4) | lnib;
}

void PGM_cycle (
  enum CMDSTAT_t * cmdstat,
  unsigned char odd_tracker
)
{
  switch(*cmdstat) {
    case CMD_DONE:
      return;                     //nothing to do

    case CMD_ACTIVE:
      CHIP_OFF;                   //force pgm-cycle, if any
      WIPcheck(T_PP);             //detect end of write cycle
      break;

    case CMD_IS_CP:
      if(odd_tracker)
        outbits(0xff, 1 << 7);    //send dummy byte which does not contain any zero bits
      CHIP_OFF;                   //force pgm-cycle, if any
      WIPcheck(T_PP >> KSR_BP);   //detect end of write cycle
      cmd_WRDI();
      CPM_polling();              //What for?
      break;
  }
  *cmdstat = CMD_DONE;
}



void ledstat (
  void *ptr
)
{
  /*
    This function controls board’s Status LEDs D1, D2 and D3.
    To be used with extra cog, to be stopped externally.

    === LED Status Cycle Timings =============================
    Time per Status Cycle (µs):       CYCLE_TIME      1000000
    Time per Loop Iteration (µs):     LOOP_TIME       1000
    Max. LED Pulses per Cycle:        PMAX            5
    ----------------------------------------------------------
    Loop Iterations per LED Phase:    PHASE_INIT      CYCLE_TIME / (LOOP_TIME * PMAX * 2)
    Loop Iterations per Cycle:        CYCLE_INIT      PMAX * PHASE_INIT * 2
    Estimated Time Stamp Error:       TSTAMPERROR     0
  */


# define CYCLE_TIME       1000000
# define LOOP_TIME        1000      //1000Hz Refresh Rate
# define PMAX             5
# define PHASE_INIT       (CYCLE_TIME / (LOOP_TIME * PMAX * 2))
# define CYCLE_INIT       (PMAX * PHASE_INIT * 2)
# define TSTAMPERROR      0

  int timestamp;
  int cycle = 0;  //forces initialization of other values
  int phase = 0;  //forces initialization of other values
  signed int D1_control;
  signed int D2_control;
  signed int D3_control;
  int D1_phases;
  //~ int D2_phases;
  //~ int D3_phases;

  while(1) {
    //get timestamp
    timestamp = CNT;

    //get high resolution update
    D1_control = xcog1.green;
    D2_control = xcog1.orange;
    D3_control = xcog1.yellow;
    if(D1_control < 1)
      D1_phases = 0;
    //~ if (D2_control < 1)
      //~ D2_phases = 0;
    //~ if(D3_control < 1)
      //~ D3_phases = 0;

    //start new phase
    if(phase == 0) {
        phase = PHASE_INIT;

      //start new cycle
      if(cycle == 0) {
        cycle = CYCLE_INIT;

        //load train pulses
        //- no need to test D1_control for positive range
        //- decrementing here as this makes bit0 odd
        D1_phases = (D1_control << 1) - 1;
        //~ D2_phases = (D2_control << 1) - 1;
        //~ D3_phases = (D3_control << 1) - 1;
      }
      else {
        //decrementing phases here, prior to test them in switch loops,
        //does work nevertheless, makes last bit odd, and saves some bytes.
        D1_phases -= (D1_phases > 0);
        //~ D2_phases -= (D2_phases > 0);
        //~ D3_phases -= (D3_phases > 0);
      }
    }

    //D1
    switch(D1_control) {
      case -1:
        high(PIN_D1);
        break;

      case 1 ... PMAX:
        if(D1_phases & 0b01)
          high(PIN_D1);
        else
          low(PIN_D1);
        break;

      default:
        low(PIN_D1);
    }

    //D2
    switch(D2_control) {
      case -1:
        high(PIN_D2);
        break;

      //~ case 1 ... PMAX:
        //~ if(D2_phases & 0b01)
          //~ high(PIN_D2);
        //~ else
          //~ low(PIN_D2);
        //~ break;

      default:
        low(PIN_D2);
        break;
    }

    //D3
    switch(D3_control) {
      case -2:
        if(input(PIN_PLUGTESTn))
          high(PIN_D3);
        else
          low(PIN_D3);
        break;

      case -1:
        high(PIN_D3);
        break;

      //~ case 1 ... PMAX:
        //~ if(D3_phases & 0b01)
          //~ high(PIN_D3);
        //~ else
          //~ low(PIN_D3);
        //~ break;

      default:
        low(PIN_D3);
        break;
    }

    //update counters
    phase--;
    cycle--;

    //loop resolution
    waitcnt(timestamp - TSTAMPERROR + CLKFREQ / 1000000 * LOOP_TIME);
  }
}



void burn (
  void * ptr
)
{
  /*
    Wite data to chip.
    
    To be used with extra cog. To be stopped externally.
    
    Parameters are organized in xcog0. Not all members have
    to be declared volatile.
    
    WARNING:
    Do not declare flags as volatile bitfield struct. It doesn't
    work.
  */


  //create local copies, which are faster to access
  unsigned char * p = xcog0.pbuf;
  unsigned char cmdinuse = xcog0.cmd;
  unsigned int pagesize = xcog0.pagesize;
  unsigned int mask_pagesize = pagesize - 1;
  unsigned char abits = get_addrlen(ADDRLEN_IN_BITS, pchipspec->size - 1);
  enum TYPEOFPAYLOAD_t type_of_payload = plinespec->type_of_payload;

  enum CMDSTAT_t cmdstat = CMD_DONE;
  int space = 0;
  int spacectr = 0;
  int queue = 0;
  unsigned char odd_tracker = 0;
  unsigned int addr = 0;
  enum {
    CHECK_FLAGS,
    CHECK_QUEUE,
    LOAD_LINEADDR,
    GO_OFF,
    HANG_UP
  } runctrl = 0;    // Run control.

  SPI_ini();        //main cog should have left the SPI chip powered!
  if(type_of_payload == PAYLOAD_SOTP_AREA)
    cmd_ENSO();
  WPn_HIGH;         //disable hardware write protection
  while(1) {
    switch(runctrl) {
      case CHECK_FLAGS:
        if(xcog0.rq_spioff) {
          runctrl = GO_OFF;           //jump
          continue;
        }
        if(xcog0.rq_lineaddr) {
          runctrl = LOAD_LINEADDR;    //jump
          continue;
        }

      case CHECK_QUEUE:
        queue = xcog0.offwr - xcog0.offrd;
        if(queue) {
          xcog0.queue_empty = 0;
          if(cmdstat != CMD_DONE)
            break;                    //jump to: load data
        }
        else {
          xcog0.queue_empty = 1;
          runctrl = CHECK_FLAGS;      //jump
          continue;
        }

        //set_space:
        space = spacectr = pagesize - (addr & mask_pagesize);

        //start_cmd:
        if(cmdstat != CMD_IS_CP) {
          cmd_WREN();                 //set Write Enable Latch Bit
          CHIP_ON;                    //start
          if(cmdinuse != CMD__CP) {
            cmd(cmdinuse, addr, abits);
            cmdstat = CMD_ACTIVE;
          }
          else {
            if(addr & 1) {
              cmd(cmdinuse, addr - 1, abits);
              outbits(0xff, 1 << 7);  //dummy byte without zero bits
              odd_tracker ^= 1;
            }
            else
              cmd(cmdinuse, addr, abits);
            cmdstat = CMD_IS_CP;
          }
        }
        else {
          CHIP_ON;                    //continue
          outbits(CMD__CP, 1 << 7);
        }
        break;                        //jump to: load data

      case LOAD_LINEADDR:
        if(addr != xcog0.lineaddr) {
          PGM_cycle(&cmdstat, odd_tracker);     //in case cmdstat != CMD_DONE
          addr = xcog0.lineaddr;                //update addr
          odd_tracker = 0;                      //initialize odd_tracker
        }
        xcog0.rq_lineaddr = 0;
        runctrl = CHECK_QUEUE;        //jump
        continue;

      case GO_OFF:
        PGM_cycle(&cmdstat, odd_tracker);       //in case cmdstat != CMD_DONE
        WPn_LOW;                                //enable hardware write protection
        if(type_of_payload == PAYLOAD_SOTP_AREA)
          cmd_EXSO();
        SPI_off(SPIOFF_SOFT);
        xcog0.rq_spioff = 0;
        runctrl = HANG_UP;            //final step, hang up

      case HANG_UP:
        continue;
    }

    //load data:
    while(spacectr && queue) {
      outbits(*(p + (xcog0.offrd & (SIZE_256 - 1))), 1 << 7);
      xcog0.offrd++;
      queue--;
      spacectr--;
      odd_tracker ^= 1;
    }
    //check space counter
    if(!spacectr) {
      PGM_cycle(&cmdstat, odd_tracker);
      addr += space;
    }
    runctrl = CHECK_FLAGS;        //jump
  }
}



int S_istype (int x)
{
  //check for valid Motorola SRecord type

  switch(x) {
    case '0' ... '3':
    case '5' ... '9':
      return 1;
    default:
      return 0;
  }
}



int S_isstart (int s)
{
  //check for Motorola S0 (start record)

  switch(s) {
    case '0':
      return 1;
    default:
      return 0;
  }
}



int S_isdata (int s)
{
  //check for Motorola S1, S2, S3 (data records)

  switch(s) {
    case '1' ... '3':
      return 1;
    default:
      return 0;
  }
}



int S_islinecount (int s)
{

  //check for Motorola S5, S6 (line count records)

  switch(s) {
    case '5':
    case '6':
      return 1;
    default:
      return 0;
  }
}



int S_isstartexec (int s)
{

  //check for Motorola S7, S8, S9 (startexec records)

  switch(s) {
    case '7' ... '9':
      return 1;
    default:
      return 0;
  }
}



int S_addrlen (int s)
{

  //return Motorola Srecord’s number of address bytes

  switch(s) {
    case '0':
    case '1':
    case '5':
    case '9':
      return 2;
    case '2':
    case '6':
    case '8':
      return 3;
    case '3':
    case '7':
      return 4;
    default:
      return -1;    //no valid SRecord type
  }
}



char rxline_HEXD (
  unsigned int * plines
)
{
  /*
    Parse a Hex-Dump line.
    
    NOTE: A '#'-character in position zero or after last data byte
    flags a garbage line.
  */


  //create local copies to be faster
  unsigned char * p = xcog0.pbuf;
  unsigned int offwr = xcog0.offwr;

  enum {
    RX_LINEADDR,
    RX_FIRSTSPACE,
    RX_DATA,
    RX_LE
  } rx = RX_LINEADDR;

  int k;      //byte selector
  int t;      //nibble selector
  int x = 0;
  int n = 0;
  int i = 0;
  int cmax = 1;
  int lineaddr = 0;
  unsigned char c[9];   //enough space to fetch address bytes in one go

  while(1) {
    if((n += cmax) >= SIZE_256)
      return ERRC__LINE_TOO_LONG;
    for(i = 0; i < cmax; i++)
      OUTC((c[i] = getChar()));
    switch(rx) {
      case RX_LINEADDR:
        if(c[0] == '#') {
          rx = RX_LE;
          continue;
        }
        if(x > 8)
          return ERRC__HEXD_PARSE_ERROR;
        if(c[0] != ':') {
          if((c[0] = hexdigit2bin(c[0])) < 0)
            return ERRC__NO_HEX_DIGIT;
          c[++x] = c[0];
        }
        else {
          k = 0;
          t = 0;
          while(x) {
            lineaddr |= (c[x--] << (t << 2)) << (k << 3);
            k += t;
            t ^= 1;
          }
          if(plinespec->type_of_payload == PAYLOAD_SOTP_AREA)
            if(lineaddr + PAYLOAD_HEXD > SIZE_SOTP)
              return ERRC__INVALID_ADDRESS;
          xcog0.lineaddr = lineaddr;
          xcog0.rq_lineaddr = 1;
          rx = RX_FIRSTSPACE;
        }
        continue;

      case RX_FIRSTSPACE:
        if(c[0] != ' ')
          return ERRC__HEXD_PARSE_ERROR;
        rx = RX_DATA;
        cmax = 3;
        continue;

      case RX_DATA:
        if(c[0] == ' ' && (c[1] == ' ' || c[1] == '#')) {
          rx = RX_LE;
          cmax = 1;
          continue;
        }
        if((c[0] = hex2bin(c[0], c[1])) < 0)
          return ERRC__NO_HEX_DIGIT;

        //add to queue
        if((offwr - xcog0.offrd) > (SIZE_256 - 1))
          return ERRC__BUFFER_OVERRUN;
        *(p + (offwr++ & (SIZE_256 - 1))) = c[0];

        if(c[2] == CLIM_HEXD)
          break;      //we are done!
        if(c[2] != ' ')
          return ERRC__HEXD_PARSE_ERROR;
        continue;

      case RX_LE:
        if(c[0] != CLIM_HEXD)
          continue;
        else
          *plines += 1;
        break;        //we are done!
    }
    xcog0.offwr = offwr;
    return ERRC__SUCCESS;
  }
}



char rxline_SREC (
  unsigned int * plines
)
{
  /*
    Receive and parse a line in Motorola S-Record format. This func
    finishes successfully after having parsed the line's checksum
    data, thus not evaluating any line ending characters.
    
    TODO:
    What about the line buffer size? Should it be set to maximum
    initially or shall we allocate it on the fly?
  */


  //create local copies, which are faster
  unsigned char * p = xcog0.pbuf;
  unsigned int offwr = xcog0.offwr;
  enum TYPEOFPAYLOAD_t type_of_payload = plinespec->type_of_payload;

  enum {
    RX_S,
    RX_STYPE,
    RX_LEN,
    RX_LINEADDR,
    RXISSTART_DATA,
    RX_DATA,
    RX_CHECKSUM
  } rx = RX_S;

  int x = 0;                        //counts address bytes
  int n = 0;                        //counts data bytes
  int vbin = 0;
  int hexbyte = 0;                  //flag
  unsigned char s = '0';
  unsigned char sum = 0;
  unsigned int lineaddr = 0;
  unsigned char c[2] = { 0, 0 };    //enough space to fetch a byte in one go

  while(1) {
    OUTC((vbin = (c[1] = getChar())));
    if(hexbyte) {
      if((vbin = hex2bin(c[0], c[1])) < 0)
        return ERRC__NO_HEX_DIGIT;
    }
    switch(rx) {
      case RX_S:
        if(c[1] != 'S')
          return ERRC__NO_SREC;
        rx = RX_STYPE;
        continue;

      case RX_STYPE:
        s = c[1];
        if((x = S_addrlen(s)) < 0)
          return ERRC__NO_SREC;
        rx = RX_LEN;
        break;                  //get c0 and c1

      case RX_LEN:
        sum = vbin;
        n = vbin - x - 1;
        if(n > SIZE_256)
          return ERRC__LINE_TOO_LONG;
        rx = RX_LINEADDR;
        break;                  //get c0 and c1

      case RX_LINEADDR:
        if(x--) {               //address in progress
          sum += vbin;
          lineaddr |= vbin << (x << 3);
        }
        if(!x) {                //done with address
          if(n) {               //any payload data?
            if(S_isdata(s)) {   //isdata
              rx = RX_DATA;
              xcog0.lineaddr = lineaddr;
              xcog0.rq_lineaddr = 1;
            }
            else {              //isstart
              rx = RXISSTART_DATA;
            }
            if(type_of_payload == PAYLOAD_BIN) {
              hexbyte = 0;        //clear flag
              continue;           //get c1 only = binary data
            }
          }
          else {
            rx = RX_CHECKSUM;
          }
        }
        break;                  //get c0 and c1

      case RX_DATA:
        if((offwr - xcog0.offrd) > (SIZE_256 - 1))
          return ERRC__BUFFER_OVERRUN;
        *(p + (offwr++ & (SIZE_256 - 1))) = vbin;

      case RXISSTART_DATA:
        sum += vbin;
        if(!--n) {
          rx = RX_CHECKSUM;
          break;                //get c0 and c1
        }
        if(type_of_payload == PAYLOAD_BIN) {
          hexbyte = 0;          //clear flag
          continue;             //get c1 only = binary data
        }
        break;                  //get c0 and c1

      case RX_CHECKSUM:
        if(vbin != (~sum & 0xff))
          return ERRC__CHECKSUM_MISMATCH;
        if(S_isdata(s))
          *plines += 1;
        else if(S_islinecount(s) && (*plines != lineaddr))
          return ERRC__LINE_COUNT_MISMATCH;
        xcog0.offwr = offwr;
        return ERRC__SUCCESS;
    }
    OUTC((c[0] = getChar()));
    hexbyte = 1;                //flag
  }
}



void report_err (
  char reported_err
)
{
  if(reported_err)
    printi(MSG_ERRCODE, reported_err);
}



enum ERRCODE_t rxfile (    //return errcode
  char screen_output
)
{
  char (* pfline) (unsigned int *);     //declare function pointer
  enum RXSTEP_t {
    RX_STX_OR_EOT,
    RX_ERROR,
    SEND_NAK,
    RX_DONE
  } rx = RX_STX_OR_EOT;
  enum ERRCODE_t errcode = ERRC__SUCCESS;
  unsigned int lines = 0;
  char c = 0;
  unsigned char s[SIZE_256];   //big enough to hold maximal payload (252), but power of two to allow ringbuffer index.

  //assign line function pointer
  switch(plinespec->linetype) {
    case LINETYPE_HEXD:   pfline = rxline_HEXD;       break;
    default:              pfline = rxline_SREC;       break;
  }

  //initialize struct pxcog0
  xcog0.pbuf = s;
  xcog0.offrd = 0;          //volatile
  xcog0.offwr = 0;          //volatile
  xcog0.lineaddr = 0;       //volatile
  xcog0.rq_spioff = 0;      //volatile
  xcog0.queue_empty = 1;    //volatile
  if(pchipspec->cmdset & (1 << X02_PP)) {
    xcog0.cmd = CMD__PP;
    xcog0.pagesize = SIZE_256;
  }
  else if(pchipspec->cmdset & (1 << XAD_CP)) {
    xcog0.cmd = CMD__CP;
    xcog0.pagesize = 2;
  }
  else if(pchipspec->cmdset & (1 << X02_BP)) {
    xcog0.cmd = CMD__BP;
    xcog0.pagesize = 1;
  }
  xcog0.pcogid = cog_run(&burn, STACK_BURNBUF);   //start burn in extra cog

  //send header
  OUTC(SOH);
  if(screen_output)
    OUTC(FILE_TO_CHIP);
  else
    OUTC(FILE_TO_CHIP_NOSCREEN);
  OUTC(plinespec->linetype);

  //process file
  do {
    switch(rx) {
      case RX_STX_OR_EOT:
        //get character
        do
          c = getChar();
        while(c != STX && c != EOT);
        if(c == EOT) {
          rx = RX_DONE;
          continue;
        }

        //feedback
        OUTC(c);

        //process line
        if((errcode = pfline(&lines))) {
          rx = RX_ERROR;
          continue;
        }

        //rx_etx_or_can:
        c = getChar();  //fetch ETX
        if(c == CAN) {
          errcode = ERRC__JOB_CANCELLATION;
          rx = SEND_NAK;
          continue;
        }
        /*
          Note connect is NOT sending any line ending characters, but:
          
          - ETX for SREC line type
          - CLIM_HEXD + ETX for HEXD line type, but the received
            CLIM_HEXD has already been processed by rxline_HEXD().
        */

        //send_LE:
        /*
          Note rxline_HEXD uses CLIM_HEXD for detection of line ending,
          and this character, i.e. a Carriage Return, has already been sent
          as feedback.
        */
        OUTC(CARR_RET);   //send CR for proper screen output
        OUTC(NEW_LINE);   //send LF for proper screen output and file line ending

        //wait_burn:
        while(!xcog0.queue_empty);

        //send_ack:
        OUTC(ACK);
        rx = RX_STX_OR_EOT;
        continue;

      case RX_ERROR:
        if(errcode != ERRC__JOB_CANCELLATION)
          do OUTC((c = getChar()));   //get c and feedback
          while(c != ETX && c != CAN);
          //  CAN?! Yes, user may hit 'q' after error occured and
          //  transmission will then end with CAN.

      case SEND_NAK:
        OUTC(NAK);
        OUTC(errcode);   //send error code
        rx = RX_STX_OR_EOT;
        continue;

      case RX_DONE:
        xcog0.rq_spioff = 1;
        while(xcog0.rq_spioff);
        cog_end(xcog0.pcogid);
        SPI_off(SPIOFF_POWER);
        return errcode;
    }
  } while(1);
}
