/* * 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. uint32_t idr; 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; } return true; } 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; } return true; } bool ARMKinetisDebug::peripheralInit() { // Enable peripheral clocks if (!memStore(REG_SIM_SCGC5, 0x00043F82)) return false; if (!memStore(REG_SIM_SCGC6, REG_SIM_SCGC6_FTM0 | REG_SIM_SCGC6_FTM1 | REG_SIM_SCGC6_FTFL)) return false; // Test AHB-AP: Can we successfully write to RAM? if (!memStoreAndVerify(0x20000000, 0x31415927)) return false; if (!memStoreAndVerify(0x20000000, 0x76543210)) return false; // 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 false; } return true; } bool ARMKinetisDebug::flashMassErase() { // Erase all flash, even if some of it is protected. uint32_t status; if (!apRead(REG_MDM_STATUS, status)) return false; 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)) return false; // 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"); return true; } 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 address = 0; 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!"); return true; } 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; }