Just4Trionic - CAN and BDM FLASH programmer for Saab cars

Dependencies:   mbed

Revision:
1:d5452e398b76
Child:
2:bf3a2b29259a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bdmtrionic.cpp	Tue Sep 14 21:02:04 2010 +0000
@@ -0,0 +1,894 @@
+/*******************************************************************************
+
+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
+            //            led_on(LED_ACT);
+            led3 = 1;
+        }
+
+        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
+        led3 = 1;
+        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
+//        led_on(LED_ACT);
+        led3 = 1;
+    }
+
+    // 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() == TERM_ERR) {
+                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) == TERM_ERR) {
+                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
+        led3 = 1;
+
+    }
+
+    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(&start_addr, 0xffff) == TERM_OK &&
+            memwrite_word(&start_addr, 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
+//        led3 = (addr & 0x400);
+        led3 = 1;
+    }
+
+    // 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
+            led3 = 1;
+        }
+    }
+    // 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
+//        wait_us(10);
+        // 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);
+    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
+//-----------------------------------------------------------------------------