Skip to content
Snippets Groups Projects
arm_kinetis_debug.cpp 14.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Simple ARM debug interface for Arduino, using the SWD (Serial Wire Debug) port.
     * Extensions for Freescale Kinetis chips.
     * 
     * Copyright (c) 2013 Micah Elizabeth Scott
     * 
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
     * the Software, and to permit persons to whom the Software is furnished to do so,
     * subject to the following conditions:
     * 
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     * 
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
     * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
     * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     */
    
    #include <Arduino.h>
    #include "arm_kinetis_debug.h"
    #include "arm_kinetis_reg.h"
    
    
    bool ARMKinetisDebug::startup()
    {
    
        return detect() && reset() && debugHalt() && peripheralInit();
    
    bool ARMKinetisDebug::detect()
    {
    
        // Make sure we're on a compatible chip. The MDM-AP peripheral is Freescale-specific.
    
        if (!apRead(REG_MDM_IDR, idr))
    
            return false;
        if (idr != 0x001C0000) {
            log(LOG_ERROR, "ARMKinetisDebug: Didn't find a supported MDM-AP peripheral");
            return false;
        }
    
    
    bool ARMKinetisDebug::reset()
    
    {
        // System resets can be slow, give them more time than the default.
        const unsigned resetRetries = 2000;
    
        // Put the control register in a known state, and make sure we aren't already in the middle of a reset
        uint32_t status;
        if (!apWrite(REG_MDM_CONTROL, REG_MDM_CONTROL_CORE_HOLD_RESET))
            return false;
        if (!apReadPoll(REG_MDM_STATUS, status, REG_MDM_STATUS_SYS_NRESET, -1, resetRetries))
            return false;
    
        // System reset
        if (!apWrite(REG_MDM_CONTROL, REG_MDM_CONTROL_SYS_RESET_REQ))
            return false;
        if (!apReadPoll(REG_MDM_STATUS, status, REG_MDM_STATUS_SYS_NRESET, 0))
            return false;
        if (!apWrite(REG_MDM_CONTROL, 0))
            return false;
    
        // Wait until the flash controller is ready & system is out of reset.
        // Also wait for security bit to be cleared. Early in reset, the chip is determining
        // its security status. When the security bit is set, AHB-AP is disabled.
        if (!apReadPoll(REG_MDM_STATUS, status,
                REG_MDM_STATUS_SYS_NRESET | REG_MDM_STATUS_FLASH_READY | REG_MDM_STATUS_SYS_SECURITY,
                REG_MDM_STATUS_SYS_NRESET | REG_MDM_STATUS_FLASH_READY,
                resetRetries))
            return false;
    
        return true;
    }
    
    bool ARMKinetisDebug::debugHalt()
    {
    
        /*
         * Enable debug, request a halt, and read back status.
         *
         * This part is somewhat timing critical, since we're racing against the watchdog
         * timer. Avoid memWait() by calling the lower-level interface directly.
         *
         * Since this is expected to fail a bunch before succeeding, mute errors temporarily.
         */
    
        unsigned haltRetries = 200;
        LogLevel savedLogLevel;
        uint32_t dhcsr;
    
    
        // Point at the debug halt control/status register. We disable MEM-AP autoincrement,
        // and leave TAR pointed at DHCSR for the entire loop.
        if (memWriteCSW(CSW_32BIT) && apWrite(MEM_TAR, REG_SCB_DHCSR)) {
    
    
            setLogLevel(LOG_NONE, savedLogLevel);
    
            while (haltRetries) {
                haltRetries--;
    
                if (!apWrite(MEM_DRW, 0xA05F0003))
                    continue;
                if (!apRead(MEM_DRW, dhcsr))
                    continue;
    
                if (dhcsr & (1 << 17)) {
                    // Halted!
                    break;
                }
    
    
            setLogLevel(savedLogLevel);
    
        if (!haltRetries) {
            log(LOG_ERROR, "ARMKinetisDebug: Failed to put CPU in debug halt state. (DHCSR: %08x)", dhcsr);
            return false;
    
    }
    
    bool ARMKinetisDebug::peripheralInit()
    {
    
        // Enable peripheral clocks
        if (!memStore(REG_SIM_SCGC5, 0x00043F82))
    
        if (!memStore(REG_SIM_SCGC6, REG_SIM_SCGC6_FTM0 | REG_SIM_SCGC6_FTM1 | REG_SIM_SCGC6_FTFL))
    
        // Test AHB-AP: Can we successfully write to RAM?
        if (!memStoreAndVerify(0x20000000, 0x31415927))
    
        if (!memStoreAndVerify(0x20000000, 0x76543210))
    
        // Test byte-wide memory access
    
        uint32_t word;
        uint8_t byte;
        if (!memStoreByte(0x20000001, 0x55))
            return false;
        if (!memStoreByte(0x20000002, 0x9F))
            return false;
        if (!memLoad(0x20000000, word))
            return false;
        if (word != 0x769F5510) {
            log(LOG_ERROR, "ARMKinetisDebug: Byte-wide AHB write seems broken! (Test word = %08x)", word);
            return false;
        }
        if (!memLoadByte(0x20000003, byte))
            return false;
        if (byte != 0x76) {
    
            log(LOG_ERROR, "ARMKinetisDebug: Byte-wide AHB read seems broken! (Test byte = %02x)", byte);
            return false;
        }
    
        // Test halfword-wide memory access
        uint16_t half;
        if (!memStoreHalf(0x20000000, 0x5abc))
            return false;
        if (!memStoreHalf(0x20000002, 0xdef0))
            return false;
        if (!memLoad(0x20000000, word))
            return false;
        if (word != 0xdef05abc) {
            log(LOG_ERROR, "ARMKinetisDebug: Halfword-wide AHB write seems broken! (Test word = %08x)", word);
            return false;
        }
        if (!memLoadHalf(0x20000002, half))
            return false;
        if (half != 0xdef0) {
            log(LOG_ERROR, "ARMKinetisDebug: Halfword-wide AHB read seems broken! (Test half = %04x)", half);
    
        return true;
    }
    
    bool ARMKinetisDebug::flashMassErase()
    {
        // Erase all flash, even if some of it is protected.
    
        uint32_t status;
    
        if (!apRead(REG_MDM_STATUS, status))
    
        if (!(status & REG_MDM_STATUS_FLASH_READY)) {
    
            log(LOG_ERROR, "FLASH: Flash controller not ready before mass erase");
            return false;
        }
    
        if ((status & REG_MDM_STATUS_FLASH_ERASE_ACK)) {
    
            log(LOG_ERROR, "FLASH: Mass erase already in progress");
            return false;
        }
    
        if (!(status & REG_MDM_STATUS_MASS_ERASE_ENABLE)) {
            log(LOG_ERROR, "FLASH: Mass erase is disabled!");
            return false;
        }
    
    
        log(LOG_NORMAL, "FLASH: Beginning mass erase operation");
    
        if (!apWrite(REG_MDM_CONTROL, REG_MDM_CONTROL_CORE_HOLD_RESET | REG_MDM_CONTROL_MASS_ERASE))
    
        // Wait for the mass erase to begin (ACK bit set)
        if (!apReadPoll(REG_MDM_STATUS, status, REG_MDM_STATUS_FLASH_ERASE_ACK, -1)) {
            log(LOG_ERROR, "FLASH: Timed out waiting for mass erase to begin");
            return false;
        }
    
        // Wait for it to complete (CONTROL bit cleared)
        uint32_t control;
        if (!apReadPoll(REG_MDM_CONTROL, control, REG_MDM_CONTROL_MASS_ERASE, 0, 10000)) {
    
            log(LOG_ERROR, "FLASH: Timed out waiting for mass erase to complete");
            return false;
        }
    
    
        // Check status again
        if (!apRead(REG_MDM_STATUS, status))
            return false;
    
        if (!(status & REG_MDM_STATUS_FLASH_READY)) {
    
            log(LOG_ERROR, "FLASH: Flash controller not ready after mass erase");
            return false;
        }
    
    
        log(LOG_NORMAL, "FLASH: Mass erase complete");
    
    
    bool ARMKinetisDebug::flashSectorBufferInit()
    {
        // Use FlexRAM as normal RAM, and erase it. Test to make sure it's working.
        return
            ftfl_setFlexRAMFunction(0xFF) &&
            memStoreAndVerify(REG_FLEXRAM_BASE, 0x12345678) &&
            memStoreAndVerify(REG_FLEXRAM_BASE, 0xFFFFFFFF) &&
            memStoreAndVerify(REG_FLEXRAM_BASE + FLASH_SECTOR_SIZE - 4, 0xA5559872) &&
            memStoreAndVerify(REG_FLEXRAM_BASE + FLASH_SECTOR_SIZE - 4, 0xFFFFFFFF);
    }
    
    
    bool ARMKinetisDebug::flashSectorBufferWrite(uint32_t bufferOffset, const uint32_t *data, unsigned count)
    
    {
        if (bufferOffset & 3) {
            log(LOG_ERROR, "ARMKinetisDebug::flashSectorBufferWrite alignment error");
            return false;
        }
        if (bufferOffset + (count * sizeof *data) > FLASH_SECTOR_SIZE) {
            log(LOG_ERROR, "ARMKinetisDebug::flashSectorBufferWrite overrun");
            return false;
        }
    
        return memStore(REG_FLEXRAM_BASE + bufferOffset, data, count);
    }
    
    bool ARMKinetisDebug::flashSectorProgram(uint32_t address)
    {
        if (address & (FLASH_SECTOR_SIZE-1)) {
            log(LOG_ERROR, "ARMKinetisDebug::flashSectorProgram alignment error");
            return false;
        }
    
        return ftfl_programSection(address, FLASH_SECTOR_SIZE/4);
    }
    
    bool ARMKinetisDebug::ftfl_busyWait()
    {
        const unsigned retries = 1000;
        uint32_t fstat;
    
        if (!memPoll(REG_FTFL_FSTAT, fstat, REG_FTFL_FSTAT_CCIF, -1)) {
            log(LOG_ERROR, "FLASH: Error waiting for flash controller");
            return false;
        }
    
        return true;
    }
    
    bool ARMKinetisDebug::ftfl_launchCommand()
    {
        // Begin a flash memory controller command, and clear any previous error status.
        return
    
            memStoreByte(REG_FTFL_FSTAT, REG_FTFL_FSTAT_ACCERR | REG_FTFL_FSTAT_FPVIOL | REG_FTFL_FSTAT_RDCOLERR) &&
            memStoreByte(REG_FTFL_FSTAT, REG_FTFL_FSTAT_CCIF);
    
    }
    
    bool ARMKinetisDebug::ftfl_setFlexRAMFunction(uint8_t controlCode)
    {
        return
            ftfl_busyWait() &&
    
            memStoreByte(REG_FTFL_FCCOB0, 0x81) &&
            memStoreByte(REG_FTFL_FCCOB1, controlCode) &&
    
            ftfl_launchCommand() &&
            ftfl_busyWait() &&
            ftfl_handleCommandStatus();
    }
    
    bool ARMKinetisDebug::ftfl_programSection(uint32_t address, uint32_t numLWords)
    {
        return
            ftfl_busyWait() &&
    
            memStoreByte(REG_FTFL_FCCOB0, 0x0B) &&
            memStoreByte(REG_FTFL_FCCOB1, address >> 16) &&
            memStoreByte(REG_FTFL_FCCOB2, address >> 8) &&
            memStoreByte(REG_FTFL_FCCOB3, address) &&
            memStoreByte(REG_FTFL_FCCOB4, numLWords >> 8) &&
            memStoreByte(REG_FTFL_FCCOB5, numLWords) &&
    
            ftfl_launchCommand() &&
            ftfl_busyWait() &&
            ftfl_handleCommandStatus("FLASH: Error verifying sector! (FSTAT: %08x)");
    }
    
    bool ARMKinetisDebug::ftfl_handleCommandStatus(const char *cmdSpecificError)
    {
        /*
         * Handle common errors from an FSTAT register value.
         * The indicated "errorMessage" is used for reporting a command-specific
         * error from MGSTAT0. Returns true on success, false on error.
         */
    
        uint32_t fstat;
        if (!memLoad(REG_FTFL_FSTAT, fstat))
            return false;
    
        if (fstat & FTFL_FSTAT_RDCOLERR) {
            log(LOG_ERROR, "FLASH: Bus collision error (FSTAT: %08x)", fstat);
            return false;
        }
    
        if (fstat & (FTFL_FSTAT_FPVIOL | FTFL_FSTAT_ACCERR)) {
            log(LOG_ERROR, "FLASH: Address access error (FSTAT: %08x)", fstat);
            return false;
        }
    
        if (cmdSpecificError && (fstat & FTFL_FSTAT_MGSTAT0)) {
            // Command-specifid error
            log(LOG_ERROR, cmdSpecificError, fstat);
            return false;
        }
    
        return true;
    }
    
    
    bool ARMKinetisDebug::flashEraseAndProgram(const uint32_t *image, unsigned numSectors)
    {
        if (!flashSectorBufferInit())
            return false;
        if (!flashMassErase())
            return false;
    
    
        // Reset again after mass erase, for new protection bits to take effect
        if (!reset())
            return false;
        if (!debugHalt())
            return false;
    
    
        uint32_t count = numSectors;
        const uint32_t *ptr = image;
    
        while (count) {
            log(LOG_NORMAL, "FLASH: Programming sector at %08x", address);
    
            if (!flashSectorBufferWrite(0, ptr, FLASH_SECTOR_SIZE/4))
    
                return false;
            if (!flashSectorProgram(address))
                return false;
    
    
            count--;
            address += FLASH_SECTOR_SIZE;
            ptr += FLASH_SECTOR_SIZE/4;
        }
    
        // Another reset! Load new protection flags.
        if (!reset())
            return false;
        if (!debugHalt())
            return false;
    
        // Verify flash memory
    
        uint32_t buffer[FLASH_SECTOR_SIZE/4];
        address = 0;
        count = numSectors;
        ptr = image;
    
        while (count) {
            log(LOG_NORMAL, "FLASH: Verifying sector at %08x", address);
    
            if (!memLoad(address, buffer, FLASH_SECTOR_SIZE/4))
                return false;
    
            bool okay = true;
            for (unsigned i = 0; i < FLASH_SECTOR_SIZE/4; i++) {
                if (buffer[i] != ptr[i]) {
                    log(LOG_ERROR, "FLASH: Verify error at %08x. Expected %08x, actual %08x",
                        address + i*4, ptr[i], buffer[i]);
                    okay = false;
                }
            }
    
            if (!okay)
                return false;
            count--;
    
            address += FLASH_SECTOR_SIZE;
    
            ptr += FLASH_SECTOR_SIZE/4;
    
        log(LOG_NORMAL, "FLASH: Programming successful!");
    
    
    static inline uint32_t gpioBitBandAddr(uint32_t addr, unsigned bit)
    {
        return (addr - 0x40000000) * 32 + bit * 4 + 0x42000000;
    }
    
    static inline uint32_t gpioPortAddr(uint32_t base, unsigned p)
    {
        return base + (p >> 12) * (REG_GPIOB_PDOR - REG_GPIOA_PDOR);
    }
    
    static inline uint32_t gpioPortBit(unsigned p)
    {
        return (p >> 2) & 31;
    }
    
    bool ARMKinetisDebug::memStoreBit(uint32_t addr, unsigned bit, uint32_t data)
    {
        return memStore(gpioBitBandAddr(addr, bit), data);
    }
    
    bool ARMKinetisDebug::memLoadBit(uint32_t addr, unsigned bit, uint32_t &data)
    {
        return memLoad(gpioBitBandAddr(addr, bit), data);
    }
    
    bool ARMKinetisDebug::pinMode(unsigned p, int mode)
    {
        // GPIO, and default drive strength + slew rate
        uint32_t pcrValue = REG_PORT_PCR_MUX(1) | REG_PORT_PCR_DSE | REG_PORT_PCR_SRE;
    
        // PCR address
        uint32_t pcrAddr = REG_PORTA_PCR0 + p;
    
        switch (mode) {
            case INPUT_PULLUP:
                // Turn on pullup
                pcrValue |= REG_PORT_PCR_PE | REG_PORT_PCR_PS;
                break;
    
            case INPUT:
            case OUTPUT:
                // Default PCR value
                break;
    
            default:
                log(LOG_ERROR, "GPIO: Unsupported pinMode %d", mode);
                return true;
        }
    
        // Set pin mode
        if (!memStore(pcrAddr, pcrValue))
            return false;
    
        // Set direction
        return memStoreBit(gpioPortAddr(REG_GPIOA_PDDR, p), gpioPortBit(p), mode == OUTPUT);
    }
    
    bool ARMKinetisDebug::digitalWrite(unsigned p, int value)
    {
        return memStoreBit(gpioPortAddr(REG_GPIOA_PDOR, p), gpioPortBit(p), value != 0);
    }
    
    int ARMKinetisDebug::digitalRead(unsigned p)
    {
        uint32_t data;
        if (!memLoadBit(gpioPortAddr(REG_GPIOA_PDIR, p), gpioPortBit(p), data))
            return -1;
        return data;
    }