Just4Trionic - CAN and BDM FLASH programmer for Saab cars

Dependencies:   mbed

bdmtrionic.cpp

Committer:
Just4pLeisure
Date:
2011-06-07
Revision:
3:92dae9083c83
Parent:
2:bf3a2b29259a
Child:
4:682d96ff6d79

File content as of revision 3:92dae9083c83:

/*******************************************************************************

bdmtrionic.cpp
(c) 2010 by Sophie Dexter

General purpose BDM functions for Just4Trionic by Just4pLeisure

A derivative work based on:
//-----------------------------------------------------------------------------
//    CAN/BDM adapter firmware
//    (C) Janis Silins, 2010
//    $id$
//-----------------------------------------------------------------------------

********************************************************************************

WARNING: Use at your own risk, sadly this software comes with no guarantees.
This software is provided 'free' and in good faith, but the author does not
accept liability for any damage arising from its use.

*******************************************************************************/

#include "bdmtrionic.h"

// structure for command address/value pairs
struct mempair_t {
    uint32_t addr;            ///< target address
    uint16_t val;            ///< word value
};

// word write algorithm (29Fxxx)
static const struct mempair_t am29_write [] = {
    {0xaaaa, 0xaaaa}, {0x5554, 0x5555}, {0xaaaa, 0xa0a0},
};

// chip erase algorithms
static const struct mempair_t am29_erase [] = {
    {0xaaaa, 0xaaaa}, {0x5554, 0x5555}, {0xaaaa, 0x8080},
    {0xaaaa, 0xaaaa}, {0x5554, 0x5555}, {0xaaaa, 0x1010}
};

// reset algorithms
//static const struct mempair_t am29_reset = {0xfffe, 0xf0f0};
static const struct mempair_t am29_reset [] = {
    {0xaaaa, 0xaaaa}, {0x5554, 0x5555}, {0xaaaa, 0xf0f0},
};

// chip id algorithms
static const struct mempair_t am29_id [] = {
    {0xaaaa, 0xaaaa}, {0x5554, 0x5555}, {0xaaaa, 0x9090},
};

// ;-)
static const struct mempair_t flash_tag [] = {
    {0x7fe00, 0xFF4A}, {0x7fe02, 0x7573}, {0x7fe04, 0x7434}, {0x7fe06, 0x704C},
    {0x7fe08, 0x6569}, {0x7fe0a, 0x7375}, {0x7fe0c, 0x7265}, {0x7fe0e, 0x3B29},
};

// local functions
bool reset_am29(void);
bool erase_am29();
bool flash_am29(const uint32_t* addr, uint16_t value);
bool reset_am28(void);
bool erase_am28(const uint32_t* start_addr, const uint32_t* end_addr);
bool flash_am28(const uint32_t* addr, uint16_t value);
bool get_flash_id(uint8_t* make, uint8_t* type);


//-----------------------------------------------------------------------------
/**
    Dumps contents of a memory block from [start_addr] up to, but not including,
    the [end_addr] as long words (word-aligned addresses). MCU must be in
    background mode. The operation interrupts if the break character is
    received.

    @param        start_addr        block start address
    @param        end_addr        block end address

    @return                        status flag
*/


uint8_t dump_flash(const uint32_t* start_addr, const uint32_t* end_addr) {

    // check parametres
    if (*start_addr > *end_addr) {
        return TERM_ERR;
    }

    // dump memory contents
    uint32_t curr_addr = *start_addr;
    uint32_t value;

    while ((curr_addr < *end_addr) && (pc.getc() != TERM_BREAK)) {
        // read long word
        if (curr_addr > *start_addr) {
            if (memdump_long(&value) != TERM_OK) {
                return TERM_ERR;
            }
        } else {
            if (memread_long(&value, &curr_addr) != TERM_OK) {
                return TERM_ERR;
            }
        }

        // send memory value to host
        printf("%08X", value);

        // add the terminating character
        if (curr_addr < *end_addr - 4) {
            pc.putc(TERM_OK);
            // light up the activity LED
            ACTIVITYLEDON;
        }

        curr_addr += 4;
    }

    return TERM_OK;
}

//-----------------------------------------------------------------------------
/**
    Dumps the contents of a T5 ECU to a BIN file on the mbed 'disk'
    from [start_addr] up to, but not including, the [end_addr].
    MCU must be in background mode.

    @param        start_addr        block start address
    @param        end_addr        block end address

    @return                        status flag
*/

uint8_t dump_trionic() {

    // Configure the MC68332 register values to prepare for flashing
    printf("I am trying to discover what type of Trionic ECU I am connected to...\r\n");
    prep_t5_do();
    // Work out what type of FLASH chips we want to make a dump file for
    uint8_t make;
    uint8_t type;
    get_flash_id(&make, &type);
    // set up chip-specific functions
    bool (*reset_func)();
    uint32_t flash_size;

    switch (type) {
        case AMD29F400B:
        case AMD29F400T:
            printf("I have found AMD29F400 type FLASH chips; I must be connected to a T7 ECU :-)\r\n");
            reset_func = &reset_am29;
            flash_size = T7FLASHSIZE;
            break;
        case AMD29F010:
            printf("I have found AMD29F010 type FLASH chips; I must be connected to a repaired T5.5 ECU :-)\r\n");
            reset_func = &reset_am29;
            flash_size = T55FLASHSIZE;
            break;
        case AMD28F010:
        case INTEL28F010:
            printf("I have found 28F010 type FLASH chips; I must be connected to a T5.5 ECU :-)\r\n");
            reset_func = &reset_am28;
            flash_size = T55FLASHSIZE;
            break;
        case AMD28F512:
        case INTEL28F512:
            printf("I have found 28F512 type FLASH chips; I must be connected to a T5.2 ECU :-)\r\n");
            reset_func = &reset_am28;
            flash_size = T52FLASHSIZE;
            break;
        default:
            // unknown flash type
            printf("I could not work out what FLASH chips or TRIONIC ECU I am connected to :-(\r\n");
            return TERM_ERR;
    }

    // reset the FLASH chips
    if (!reset_func()) return TERM_ERR;

    printf("Creating FLASH dump file...\r\n");
    FILE *fp = fopen("/local/original.bin", "w");    // Open "original.bin" on the local file system for writing
    if (!fp) {
        perror ("The following error occured");
        return TERM_ERR;
    }

// dump memory contents
    uint32_t addr = 0x00;
    uint32_t long_value;

// setup start address to dump from
    if (memread_long_cmd(&addr) != TERM_OK) return TERM_ERR;

    timer.reset();
    timer.start();

    while (addr < flash_size) {
        uint16_t byte_count = 0;
        while (byte_count < FILE_BUF_LENGTH) {
            // get long word
            if (memget_long(&long_value) != TERM_OK) return TERM_ERR;
            addr += 4;
            // send memory value to file_buffer before saving to mbed 'disk'
            file_buffer[byte_count] = ((uint8_t)(long_value >> 24));
            file_buffer[byte_count+1] = ((uint8_t)(long_value >> 16));
            file_buffer[byte_count+2] = ((uint8_t)(long_value >> 8));
            file_buffer[byte_count+3] = ((uint8_t)long_value);
            byte_count +=4;
            // make the activity led twinkle
            ACTIVITYLEDON; 
        }
        fwrite(file_buffer, 1, FILE_BUF_LENGTH, fp);
        if (ferror (fp)) {
            fclose (fp);
            printf ("Error writing to the FLASH BIN file.\r\n");
            return TERM_ERR;
        }
    }
    // should 'clear' the BDM connection here but bdm_clear won't compile from here
    // instead do a memread (or anything really) but ignore the result because it's not needed for anything
    memread_long(&long_value, &addr);
    timer.stop();
    printf("Getting the FLASH dump took %#.1f seconds.\r\n",timer.read());
    fclose(fp);
    return TERM_OK;
}

//-----------------------------------------------------------------------------
/**
    Erases the flash memory chip starting from [start_addr] up to, but not
    including [end_addr] and optionally verifies the result; MCU must be in
    background mode.

    @param        flash_type        type of flash chip
    @param        start_addr        flash start address
    @param        end_addr        flash end address

    @return                        status flag
*/
uint8_t erase_flash(const char* flash_type, const uint32_t* start_addr,
                    const uint32_t* end_addr) {
    // AM29Fxxx chips (retrofitted to Trionic 5.x; original to T7)
    if (strncmp(flash_type, "29f010", 6) == 0 ||
            strncmp(flash_type, "29f400", 6) == 0) {
        return erase_am29() ? TERM_OK : TERM_ERR;
    }

    // AM28F010 chip (Trionic 5.x original)
    if (strncmp(flash_type, "28f010", 6) == 0) {
        return erase_am28(start_addr, end_addr) ? TERM_OK : TERM_ERR;
    }

    return TERM_ERR;
}

//-----------------------------------------------------------------------------
/**
    Writes a batch of long words to the flash starting from [start_addr]. The
    operation interrupts if a break character is received. MCU must be in
    background mode.

    @param        flash_type        type of flash chip
    @param        start_addr        block start address

    @return                        status flag
*/
uint8_t write_flash(const char* flash_type, const uint32_t* start_addr) {
    // set up chip-specific functions
    bool (*reset_func)(void);
    bool (*flash_func)(const uint32_t*, uint16_t);

    // AM29Fxxx chips (retrofitted to Trionic 5.x, original to T7)
    if (strncmp(flash_type, "29f010", 6) == 0 ||
            strncmp(flash_type, "29f400", 6) == 0) {
        reset_func = &reset_am29;
        flash_func = &flash_am29;
    } else if (strncmp(flash_type, "28f010", 6) == 0) {
        // AM28F010 chip (Trionic 5.x original)
        reset_func = &reset_am28;
        flash_func = &flash_am28;
    } else {
        // unknown flash type
        return TERM_ERR;
    }

    // reset the flash
    if (!reset_func()) {
        return TERM_ERR;
    }

    uint32_t curr_addr = *start_addr;
    if (strncmp(flash_type, "29f010", 6) == 0) {
        curr_addr = 0;
    }

    int rx_char = 0;
    char rx_buf[8];
    char* rx_ptr;
    uint32_t long_value;
    bool ret = true;

    // ready to receive data
    pc.putc(TERM_OK);

    while (true) {
        // receive long words from USB
        printf("receive long words from USB\r\n");
        rx_ptr = rx_buf;
        do {
            rx_char = pc.getc();
            if (rx_char != EOF) {
                // have got all characters for one long word
                if (rx_ptr > &rx_buf[7]) {
                    ret = (rx_char == TERM_OK);
                    break;
                }

                // save the character
                *rx_ptr++ = (char)rx_char;
            }
        } while (rx_char != TERM_OK && rx_char != TERM_BREAK);
        // end writing
        printf("end writing\r\n");
        if (!ret || rx_char == TERM_BREAK) {
            break;
        }

        // convert value to long word
        printf("convert value to long word\r\n");
        if (!ascii2int(&long_value, rx_buf, 8)) {
            ret = false;
            break;
        }
        printf("long value %08x \r\n", long_value);

        // write the first word
        printf("write the first word\r\n");
        if (!flash_func(&curr_addr, (uint16_t)(long_value >> 16))) {
            ret = false;
            break;
        }
        curr_addr += 2;
        // write the second word
        printf("write the second word\r\n");
        if (!flash_func(&curr_addr, (uint16_t)long_value)) {
            ret = false;
            break;
        }
        curr_addr += 2;

        // light up the activity LED
        ACTIVITYLEDON; 
    }

    // reset flash
    return (reset_func() && ret) ? TERM_OK : TERM_ERR;
}

//-----------------------------------------------------------------------------
/**
    Writes a BIN file to the flash starting from [start_addr].
    The operation ends when no more bytes can be read from the BIN file.
    MCU must be in background mode.

    @param        flash_type        type of flash chip
    @param        start_addr        block start address

    @return                        status flag
*/
uint8_t flash_trionic() {
    // Configure the MC68332 register values to prepare for flashing
    printf("I am trying to discover what type of Trionic ECU I am connected to...\r\n");
    prep_t5_do();
    // Work out what type of FLASH chips we want to program
    uint8_t make;
    uint8_t type;
    get_flash_id(&make, &type);
    // set up chip-specific functions
    bool (*reset_func)();
    bool (*flash_func)(const uint32_t*, uint16_t);
    uint32_t flash_size;

    switch (type) {
        case AMD29F400B:
        case AMD29F400T:
            printf("I have found AMD29F400 type FLASH chips; I must be connected to a T7 ECU :-)\r\n");
            reset_func = &reset_am29;
            flash_func = &flash_am29;
            flash_size = T7FLASHSIZE;
            break;
        case AMD29F010:
            printf("I have found AMD29F010 type FLASH chips; I must be connected to a repaired T5.5 ECU :-)\r\n");
            reset_func = &reset_am29;
            flash_func = &flash_am29;
            flash_size = T55FLASHSIZE;
            break;
        case AMD28F010:
        case INTEL28F010:
            printf("I have found 28F010 type FLASH chips; I must be connected to a T5.5 ECU :-)\r\n");
            reset_func = &reset_am28;
            flash_func = &flash_am28;
            flash_size = T55FLASHSIZE;
            break;
        case AMD28F512:
        case INTEL28F512:
            printf("I have found 28F512 type FLASH chips; I must be connected to a T5.2 ECU :-)\r\n");
            reset_func = &reset_am28;
            flash_func = &flash_am28;
            flash_size = T52FLASHSIZE;
            break;
        default:
            // unknown flash type
            printf("I could not work out what FLASH chips or TRIONIC ECU I am connected to :-(\r\n");
            return TERM_ERR;
    }

    // reset the FLASH chips
    if (!reset_func()) return TERM_ERR;

    printf("Checking the FLASH BIN file...\r\n");
    FILE *fp = fopen("/local/modified.hex", "r");    // Open "modified.hex" on the local file system for reading
    if (!fp) {
        printf("Error: I could not find the BIN file MODIFIED.HEX\r\n");;
        return TERM_ERR;
    }
    // obtain file size - it should match the size of the FLASH chips:
    fseek (fp , 0 , SEEK_END);
    uint32_t file_size = ftell (fp);
    rewind (fp);

    // read the initial stack pointer value in the BIN file - it should match the value expected for the type of ECU
    uint8_t stack_byte = 0;
    uint32_t stack_long = 0;
    if (!fread(&stack_byte,1,1,fp)) return TERM_ERR;
    stack_long |= (stack_byte << 24);
    if (!fread(&stack_byte,1,1,fp)) return TERM_ERR;
    stack_long |= (stack_byte << 16);
    if (!fread(&stack_byte,1,1,fp)) return TERM_ERR;
    stack_long |= (stack_byte << 8);
    if (!fread(&stack_byte,1,1,fp)) return TERM_ERR;
    stack_long |= stack_byte;
    rewind (fp);

    if (flash_size == T52FLASHSIZE && (file_size != T52FLASHSIZE || stack_long != T5POINTER)) {
        fclose(fp);
        printf("The BIN file does not appear to be for a T5.2 ECU :-(\r\n");
        printf("BIN file size: %#10x, FLASH chip size: %#010x, Pointer: %#10x.\r\n", file_size, flash_size, stack_long);
        return TERM_ERR;
    }
    if (flash_size == T55FLASHSIZE && (file_size != T55FLASHSIZE || stack_long != T5POINTER)) {
        fclose(fp);
        printf("The BIN file does not appear to be for a T5.5 ECU :-(\r\n");
        printf("BIN file size: %#10x, FLASH chip size: %#010x, Pointer: %#10x.\r\n", file_size, flash_size, stack_long);
        return TERM_ERR;
    }
    if (flash_size == T7FLASHSIZE && (file_size != T7FLASHSIZE || stack_long != T7POINTER)) {
        fclose(fp);
        printf("The BIN file does not appear to be for a T7 ECU :-(\r\n");
        printf("BIN file size: %#10x, FLASH chip size: %#010x, Pointer: %#10x.\r\n", file_size, flash_size, stack_long);
        return TERM_ERR;
    }

    timer.reset();
    timer.start();

    uint32_t curr_addr = 0;

    switch (type) {
            // AM29Fxxx chips (retrofitted to Trionic 5.x; original to T7)
        case AMD29F400B:
        case AMD29F400T:
        case AMD29F010:
            printf("Erasing 29F400/010 type FLASH chips...\r\n");
            if (!erase_am29()) {
                printf("WARNING: An error occured when I tried to erase the FLASH chips :-(\r\n");
                return TERM_ERR;
            }
            break;
            // AM28F010 chip (Trionic 5.x original)
        case AMD28F010:
        case INTEL28F010:
        case AMD28F512:
        case INTEL28F512:
            printf("Erasing 28F010/512 type FLASH chips...\r\n");
            if (!erase_am28(&curr_addr, &flash_size)) {
                printf("WARNING: An error occured when I tried to erase the FLASH chips :-(\r\n");
                return TERM_ERR;
            }
            break;
        default:
            // unknown flash type - shouldn't get here hence "Starange!"
            printf("Strange! I couldn't work out how to erase the FLASH chips in the TRIONIC ECU that I am connected to :-(\r\n");
            return TERM_ERR;
    }

    timer.stop();
    printf("Erasing took %#.1f seconds.\r\n",timer.read());

    printf("Programming the FLASH chips...\r\n");

    timer.reset();
    timer.start();

    uint16_t word_value = 0;
    uint8_t byte_value = 0;
//    bool ret = true;

// ready to receive data
    while (curr_addr < flash_size) {
        // receive bytes from BIN file
        //Get a byte - break if no more bytes to get
        if (!fread(&byte_value,1,1,fp)) {
            fclose(fp);
            printf("Error reading the BIN file MODIFIED.HEX");
            break;
        }
        word_value = (byte_value << 8);
        if (!fread(&byte_value,1,1,fp)) {
            fclose(fp);
            printf("Error reading the BIN file MODIFIED.HEX");
            break;
        }
        word_value |= byte_value;

        // write the word if it is not 0xffff
        if (word_value != 0xffff) {
            if (!flash_func(&curr_addr, word_value)) break;
        }
        curr_addr += 2;

        // make the activity LED twinkle
        ACTIVITYLEDON; 
    }

    timer.stop();
    fclose(fp);

    if (curr_addr == flash_size) {
        printf("Programming took %#.1f seconds.\r\n",timer.read());
        reset_func();
        for (uint8_t i = 0; i < 8; ++i) {
            memread_word(&word_value, &flash_tag[i].addr);
            flash_func(&flash_tag[i].addr, (flash_tag[i].val & word_value));
        }

    } else {
        printf("WARNING: Oh dear, I couldn't program the FLASH at address 0x%8x.\r\n", curr_addr);
    }

// reset flash
    return (reset_func() && (curr_addr == flash_size)) ? TERM_OK : TERM_ERR;
}

//-----------------------------------------------------------------------------
/**
    Resets an AM29Fxxx flash memory chip. MCU must be in background mode.

    @param                          none

    @return                         succ / fail
*/
bool reset_am29(void) {
    // execute the reset command
//    uint32_t addr = 0xfffe;
//    return (memwrite_word(&addr, 0xf0f0) == TERM_OK);
    // execute the algorithm
    for (uint8_t i = 0; i < 3; ++i) {
        if (memwrite_word(&am29_reset[i].addr, am29_reset[i].val) != TERM_OK) return false;
    }
    return true;
}

//-----------------------------------------------------------------------------
/**
    Erases an AM29Fxxx flash memory chip and verifies the result; MCU must be
    in background mode.

    @return                        succ / fail
*/
bool erase_am29() {
    // reset flash
    if (!reset_am29()) {
        return false;
    }

    // execute the algorithm
    for (uint8_t i = 0; i < 6; ++i) {
        if (memwrite_word(&am29_erase[i].addr, am29_erase[i].val) != TERM_OK) {
            reset_am29();
            return false;
        }
    }

    // verify the result
    uint32_t addr = 0x0;
    uint16_t verify_value;

    uint8_t err_cnt = ERR_COUNT;
    while (--err_cnt) {
        // typical erase time = 1s
        // Allow up to 25.5 seconds erase time
        wait_ms(100);
        if (memread_word(&verify_value, &addr) == TERM_OK && verify_value == 0xffff) {
            // erase completed normally
            reset_am29();
            return true;
        }
    }

    // erase failed
    reset_am29();
    return false;
}

//-----------------------------------------------------------------------------
/**
    Writes a word to AM29Fxxx flash memory chip and optionally verifies the
    result; MCU must be in background mode.

    @param        addr        destination address
    @param        val            value

    @return                    succ / fail
*/
bool flash_am29(const uint32_t* addr, uint16_t value) {

    // execute the algorithm
    for (uint8_t i = 0; i < 3; ++i) {
        if (memwrite_word(&am29_write[i].addr, am29_write[i].val) != TERM_OK) {
            reset_am29();
            return false;
        }
    }
    // write the value
    if (memwrite_word(addr, value) != TERM_OK) {
        reset_am29();
        return false;
    }
    // verify the result
    uint8_t err_cnt = ERR_COUNT;
    while (--err_cnt) {
        // Allow up to approx 2.55 milliseconds program time (255 * ~10us BDM memread time)
//        wait_ms(10);
        uint16_t verify_value;
        if ((memread_word(&verify_value, addr) == TERM_OK) &&
                (verify_value == value)) {
            // flashing successful
            return true;
        }
    }
    // writing failed
    reset_am29();
    return false;
}

//-----------------------------------------------------------------------------
/**
    Resets a AM28Fxxx flash memory chip. MCU must be in background mode.

    @param      start_addr      flash start address

    @return                     succ / fail
*/
bool reset_am28(void) {
    uint32_t start_addr = 0x0;
    return (memwrite_word_write_word(&start_addr, 0xffff, 0xffff) == TERM_OK);
}

//-----------------------------------------------------------------------------
/**
    Erases an AM28Fxxx flash memory chip and verifies the result; MCU must be
    in background mode.

    @param      start_addr      flash start address
    @param      end_addr        flash end address

    @return                     succ / fail
*/
bool erase_am28(const uint32_t* start_addr, const uint32_t* end_addr) {

    // check the addresses
    if (!start_addr || !end_addr) return false;

    // reset flash
    if (!reset_am28()) return false;

    // write zeroes over entire flash space
    uint32_t addr = *start_addr;

    while (addr < *end_addr) {
        if (!flash_am28(&addr, 0x0000)) return false;
        addr += 2;
//        // feedback to host computer
//        pc.putc(TERM_OK);
        // make the activity LED twinkle
        ACTIVITYLEDON; 

    }

    // erase flash
    addr = *start_addr;
    uint8_t verify_value;

    uint16_t pulse_cnt = 0;
    if (memwrite_byte_cmd(NULL) != TERM_OK) {
        reset_am28();
        return false;
    }
    while ((++pulse_cnt < 1000) && (addr < *end_addr)) {
        // issue the erase command
        if (memwrite_write_byte(&addr, 0x20) != TERM_OK ||
                memwrite_write_byte(&addr, 0x20) != TERM_OK) break;
        wait_ms(10);

        while (addr < *end_addr) {
            // issue the verify command
            if (memwrite_read_byte(&addr, 0xa0) != TERM_OK) break;
//            wait_us(6);
            // check the written value
            if (memread_write_byte(&verify_value, &addr) != TERM_OK) break;
            if (verify_value != 0xff) break;
            // succeeded need to check next address
            addr++;
            // make the activity LED twinkle
            ACTIVITYLEDON; 
        }
    }
    // the erase process ends with a BDM_WRITE + BDM_BYTESIZE command left in the BDM
    // it is safe to use it to put one of the FLASH chips into read mode and thereby
    // leave the BDM ready for the next command
    memwrite_nop_byte(start_addr, 0x00);

    reset_am28();
    // check for success
    return (addr == *end_addr) ? true : false;
}

//-----------------------------------------------------------------------------
/**
    Writes a byte to AM28Fxxx flash memory chip and verifies the result
    A so called 'mask' method checks the FLASH contents and only tries
    to program bytes that need to be programmed.
    MCU must be in background mode.

    @param      addr        destination address
    @param      val         value

    @return                 succ / fail
*/
bool flash_am28(const uint32_t* addr, uint16_t value) {

    if (!addr) return false;

    uint8_t pulse_cnt = 0;
    uint16_t verify_value = 0;
    uint16_t mask_value = 0xffff;

    // put flash into read mode and read address
    if (memwrite_word_read_word(&verify_value, addr, 0x0000) != TERM_OK)  return false;
    // return if FLASH already has the correct value - e.g. not all of the FLASH is used and is 0xff
    if (verify_value == value) return true;

    while (++pulse_cnt < 25) {

        // set a mask
        if ((uint8_t)verify_value == (uint8_t)value)
            mask_value &= 0xff00;
        if ((uint8_t)(verify_value >> 8) == (uint8_t)(value >> 8))
            mask_value &= 0x00ff;

        // write the new value
        if (memwrite_word_write_word(addr, (0x4040 & mask_value), value) != TERM_OK) break;
        // NOTE the BDM interface is slow enough that there is no need for a 10us delay before verifying
        // issue the verification command
        // NOTE the BDM interface is slow enough that there is no need for a 6us delay before reading back
        if (memwrite_word_read_word(&verify_value, addr, (0xc0c0 & mask_value)) != TERM_OK) break;
        // check if flashing was successful;
        if (verify_value == value) return true;
    }

    // something went wrong; reset the flash chip and return failed
    reset_am28();
    return false;
}

//-----------------------------------------------------------------------------
/**
    Does the equivalent of do prept5.do in BD32
    Sets up all of the control registers in the MC68332 so that we can program
    the FLASH chips

    @param                  none

    @return                 succ / fail
*/

uint8_t prep_t5_do(void) {

    // reset and freeze the MC68332 chip
    if (restart_chip() != TERM_OK) return TERM_ERR;

    // set the 'fc' registers to allow supervisor mode access
    uint32_t long_value = 0x05;
    if (sysreg_write(0x0e, &long_value) != TERM_OK) return TERM_ERR;
    if (sysreg_write(0x0f, &long_value) != TERM_OK) return TERM_ERR;

    // Set MC68332 to 16 MHz (actually 16.78 MHz)
    long_value = 0x00fffa04;
    if (memwrite_word(&long_value, 0x7f00) != TERM_OK) return TERM_ERR;

    // Disable watchdog and monitors
    long_value = 0x00fffa21;
    if (memwrite_byte(&long_value, 0x00) != TERM_OK) return TERM_ERR;

    // Chip select pin assignments
    long_value = 0x00fffa44;
    if (memwrite_word(&long_value, 0x3fff) != TERM_OK) return TERM_ERR;

    // Boot Chip select read only, one wait state
    long_value = 0x00fffa48;
    if (memwrite_word(&long_value, 0x0007) != TERM_OK) return TERM_ERR;
    if (memfill_word(0x6870) != TERM_OK) return TERM_ERR;

    // Chip select 1 and 2 upper lower bytes, zero wait states
    long_value = 0x00fffa50;
    if (memwrite_word(&long_value, 0x0007) != TERM_OK) return TERM_ERR;
    if (memfill_word(0x3030) != TERM_OK) return TERM_ERR;
    if (memfill_word(0x0007) != TERM_OK) return TERM_ERR;
    if (memfill_word(0x5030) != TERM_OK) return TERM_ERR;

    // PQS Data - turn on VPPH
    long_value = 0x00fffc14;
    if (memwrite_word(&long_value, 0x0040) != TERM_OK) return TERM_ERR;

    // PQS Data Direction output
    long_value = 0x00fffc17;
    if (memwrite_byte(&long_value, 0x40) != TERM_OK) return TERM_ERR;
    // wait for programming voltage to be ready
    wait_ms(10);

//    // Enable internal 2kByte RAM of 68332 at address 0x00100000
//    long_value = 0x00fffb04;
//    if (memwrite_word(&long_value, 0x1000) != TERM_OK) return TERM_ERR;
    return TERM_OK;
}

//-----------------------------------------------------------------------------
/**
    Works out what type of flash chip is fitted in the ECU by reading
    the manufacturer byte codes.
    It is enough to use the 29Fxxx flash id algorithm because 28Fxxx
    FLASH chips ignore the first few writes needed by the 29Fxxx chips
    MCU must be in background mode.

    @param                  make (out)
                            type (out)

    @return                 succ / fail
*/
bool get_flash_id(uint8_t* make, uint8_t* type) {

    uint32_t  addr = 0x0;
    uint32_t value;
    bool ret;
    // read id bytes algorithm for 29F010/400 FLASH chips
    for (uint8_t i = 0; i < 3; ++i) {
        if (memwrite_word(&am29_id[i].addr, am29_id[i].val) != TERM_OK) return false;
    }
    if (memread_long(&value, &addr) != TERM_OK) return false;
//    *make = (uint8_t)(value >> 24);
//    *type = (uint8_t)(value >> 8);
    *make = (uint8_t)(value >> 16);
    *type = (uint8_t)(value);
    printf("FLASH id bytes: %08x, make: %02x, type: %02x\r\n", value, *make, *type);
    switch (*type) {
        case AMD29F400B:
        case AMD29F400T:
        case AMD29F010:
        case AMD28F010:
        case INTEL28F010:
        case AMD28F512:
        case INTEL28F512:
            ret = true;
        default:
            ret = false;
    }
    return ret;
}

//-----------------------------------------------------------------------------
//    EOF
//-----------------------------------------------------------------------------