-/*\r
- LUFA Library\r
- Copyright (C) Dean Camera, 2010.\r
- \r
- dean [at] fourwalledcubicle [dot] com\r
- www.fourwalledcubicle.com\r
-*/\r
-\r
-/*\r
- Copyright 2010 Dean Camera (dean [at] fourwalledcubicle [dot] com)\r
-\r
- Permission to use, copy, modify, distribute, and sell this \r
- software and its documentation for any purpose is hereby granted\r
- without fee, provided that the above copyright notice appear in \r
- all copies and that both that the copyright notice and this\r
- permission notice and warranty disclaimer appear in supporting \r
- documentation, and that the name of the author not be used in \r
- advertising or publicity pertaining to distribution of the \r
- software without specific, written prior permission.\r
-\r
- The author disclaim all warranties with regard to this\r
- software, including all implied warranties of merchantability\r
- and fitness. In no event shall the author be liable for any\r
- special, indirect or consequential damages or any damages\r
- whatsoever resulting from loss of use, data or profits, whether\r
- in an action of contract, negligence or other tortious action,\r
- arising out of or in connection with the use or performance of\r
- this software.\r
-*/\r
-\r
-/** \file\r
- *\r
- * ISP Protocol handler, to process V2 Protocol wrapped ISP commands used in Atmel programmer devices.\r
- */\r
-\r
-#include "ISPProtocol.h"\r
-\r
-#if defined(ENABLE_ISP_PROTOCOL) || defined(__DOXYGEN__)\r
-\r
-/** Handler for the CMD_ENTER_PROGMODE_ISP command, which attempts to enter programming mode on\r
- * the attached device, returning success or failure back to the host.\r
- */\r
-void ISPProtocol_EnterISPMode(void)\r
-{\r
- struct\r
- {\r
- uint8_t TimeoutMS;\r
- uint8_t PinStabDelayMS;\r
- uint8_t ExecutionDelayMS;\r
- uint8_t SynchLoops;\r
- uint8_t ByteDelay;\r
- uint8_t PollValue;\r
- uint8_t PollIndex;\r
- uint8_t EnterProgBytes[4];\r
- } Enter_ISP_Params;\r
- \r
- Endpoint_Read_Stream_LE(&Enter_ISP_Params, sizeof(Enter_ISP_Params), NO_STREAM_CALLBACK);\r
-\r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
-\r
- uint8_t ResponseStatus = STATUS_CMD_FAILED;\r
- \r
- CurrentAddress = 0;\r
- \r
- /* Set up the synchronous USART to generate the recovery clock on XCK pin */\r
- UBRR1 = (F_CPU / 500000UL);\r
- UCSR1B = (1 << TXEN1);\r
- UCSR1C = (1 << UMSEL10) | (1 << UPM11) | (1 << USBS1) | (1 << UCSZ11) | (1 << UCSZ10) | (1 << UCPOL1);\r
- DDRD |= (1 << 5);\r
-\r
- /* Perform execution delay, initialize SPI bus */\r
- ISPProtocol_DelayMS(Enter_ISP_Params.ExecutionDelayMS); \r
- SPI_Init(ISPTarget_GetSPIPrescalerMask() | SPI_SCK_LEAD_RISING | SPI_SAMPLE_LEADING | SPI_MODE_MASTER);\r
-\r
- /* Continuously attempt to synchronize with the target until either the number of attempts specified\r
- * by the host has exceeded, or the the device sends back the expected response values */\r
- while (Enter_ISP_Params.SynchLoops-- && (ResponseStatus == STATUS_CMD_FAILED) && TimeoutMSRemaining)\r
- {\r
- uint8_t ResponseBytes[4];\r
-\r
- ISPTarget_ChangeTargetResetLine(true);\r
- ISPProtocol_DelayMS(Enter_ISP_Params.PinStabDelayMS);\r
-\r
- for (uint8_t RByte = 0; RByte < sizeof(ResponseBytes); RByte++)\r
- {\r
- ISPProtocol_DelayMS(Enter_ISP_Params.ByteDelay);\r
- ResponseBytes[RByte] = SPI_TransferByte(Enter_ISP_Params.EnterProgBytes[RByte]);\r
- }\r
- \r
- /* Check if polling disabled, or if the polled value matches the expected value */\r
- if (!(Enter_ISP_Params.PollIndex) || (ResponseBytes[Enter_ISP_Params.PollIndex - 1] == Enter_ISP_Params.PollValue))\r
- {\r
- ResponseStatus = STATUS_CMD_OK;\r
- }\r
- else\r
- {\r
- ISPTarget_ChangeTargetResetLine(false);\r
- ISPProtocol_DelayMS(Enter_ISP_Params.PinStabDelayMS);\r
- }\r
- }\r
-\r
- Endpoint_Write_Byte(CMD_ENTER_PROGMODE_ISP);\r
- Endpoint_Write_Byte(ResponseStatus);\r
- Endpoint_ClearIN();\r
-}\r
-\r
-/** Handler for the CMD_LEAVE_ISP command, which releases the target from programming mode. */\r
-void ISPProtocol_LeaveISPMode(void)\r
-{\r
- struct\r
- {\r
- uint8_t PreDelayMS;\r
- uint8_t PostDelayMS;\r
- } Leave_ISP_Params;\r
-\r
- Endpoint_Read_Stream_LE(&Leave_ISP_Params, sizeof(Leave_ISP_Params), NO_STREAM_CALLBACK);\r
- \r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
-\r
- /* Perform pre-exit delay, release the target /RESET, disable the SPI bus and perform the post-exit delay */\r
- ISPProtocol_DelayMS(Leave_ISP_Params.PreDelayMS);\r
- ISPTarget_ChangeTargetResetLine(false);\r
- SPI_ShutDown();\r
- ISPProtocol_DelayMS(Leave_ISP_Params.PostDelayMS);\r
-\r
- /* Turn off the synchronous USART to terminate the recovery clock on XCK pin */\r
- UBRR1 = (F_CPU / 500000UL);\r
- UCSR1B = (1 << TXEN1);\r
- UCSR1C = (1 << UMSEL10) | (1 << UPM11) | (1 << USBS1) | (1 << UCSZ11) | (1 << UCSZ10) | (1 << UCPOL1);\r
- DDRD &= ~(1 << 5);\r
-\r
- Endpoint_Write_Byte(CMD_LEAVE_PROGMODE_ISP);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
- Endpoint_ClearIN();\r
-}\r
-\r
-/** Handler for the CMD_PROGRAM_FLASH_ISP and CMD_PROGRAM_EEPROM_ISP commands, writing out bytes,\r
- * words or pages of data to the attached device.\r
- *\r
- * \param[in] V2Command Issued V2 Protocol command byte from the host\r
- */\r
-void ISPProtocol_ProgramMemory(uint8_t V2Command)\r
-{\r
- struct\r
- {\r
- uint16_t BytesToWrite;\r
- uint8_t ProgrammingMode;\r
- uint8_t DelayMS;\r
- uint8_t ProgrammingCommands[3];\r
- uint8_t PollValue1;\r
- uint8_t PollValue2;\r
- uint8_t ProgData[256]; // Note, the Jungo driver has a very short ACK timeout period, need to buffer the\r
- } Write_Memory_Params; // whole page and ACK the packet as fast as possible to prevent it from aborting\r
- \r
- Endpoint_Read_Stream_LE(&Write_Memory_Params, (sizeof(Write_Memory_Params) -\r
- sizeof(Write_Memory_Params.ProgData)), NO_STREAM_CALLBACK);\r
-\r
-\r
- Write_Memory_Params.BytesToWrite = SwapEndian_16(Write_Memory_Params.BytesToWrite);\r
- \r
- if (Write_Memory_Params.BytesToWrite > sizeof(Write_Memory_Params.ProgData))\r
- {\r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
-\r
- Endpoint_Write_Byte(V2Command);\r
- Endpoint_Write_Byte(STATUS_CMD_FAILED);\r
- Endpoint_ClearIN();\r
- return;\r
- }\r
- \r
- Endpoint_Read_Stream_LE(&Write_Memory_Params.ProgData, Write_Memory_Params.BytesToWrite, NO_STREAM_CALLBACK);\r
-\r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
-\r
- uint8_t ProgrammingStatus = STATUS_CMD_OK; \r
- uint16_t PollAddress = 0;\r
- uint8_t PollValue = (V2Command == CMD_PROGRAM_FLASH_ISP) ? Write_Memory_Params.PollValue1 :\r
- Write_Memory_Params.PollValue2;\r
- uint8_t* NextWriteByte = Write_Memory_Params.ProgData;\r
-\r
- /* Check to see if the host has issued a SET ADDRESS command and we haven't sent a\r
- * LOAD EXTENDED ADDRESS command (if needed, used when operating beyond the 128KB\r
- * FLASH barrier) */\r
- if (MustSetAddress)\r
- {\r
- if (CurrentAddress & (1UL << 31))\r
- ISPTarget_LoadExtendedAddress();\r
-\r
- MustSetAddress = false;\r
- }\r
-\r
- /* Check the programming mode desired by the host, either Paged or Word memory writes */\r
- if (Write_Memory_Params.ProgrammingMode & PROG_MODE_PAGED_WRITES_MASK)\r
- {\r
- uint16_t StartAddress = (CurrentAddress & 0xFFFF);\r
- \r
- /* Paged mode memory programming */\r
- for (uint16_t CurrentByte = 0; CurrentByte < Write_Memory_Params.BytesToWrite; CurrentByte++)\r
- {\r
- bool IsOddByte = (CurrentByte & 0x01);\r
- uint8_t ByteToWrite = *(NextWriteByte++);\r
- \r
- SPI_SendByte(Write_Memory_Params.ProgrammingCommands[0]);\r
- SPI_SendByte(CurrentAddress >> 8);\r
- SPI_SendByte(CurrentAddress & 0xFF);\r
- SPI_SendByte(ByteToWrite);\r
- \r
- /* AVR FLASH addressing requires us to modify the write command based on if we are writing a high\r
- * or low byte at the current word address */\r
- if (V2Command == CMD_PROGRAM_FLASH_ISP)\r
- Write_Memory_Params.ProgrammingCommands[0] ^= READ_WRITE_HIGH_BYTE_MASK;\r
-\r
- /* Check to see the write completion method, to see if we have a valid polling address */\r
- if (!(PollAddress) && (ByteToWrite != PollValue))\r
- {\r
- if (IsOddByte && (V2Command == CMD_PROGRAM_FLASH_ISP))\r
- Write_Memory_Params.ProgrammingCommands[2] |= READ_WRITE_HIGH_BYTE_MASK;\r
-\r
- PollAddress = (CurrentAddress & 0xFFFF); \r
- } \r
-\r
- if (IsOddByte || (V2Command == CMD_PROGRAM_EEPROM_ISP))\r
- CurrentAddress++;\r
- }\r
- \r
- /* If the current page must be committed, send the PROGRAM PAGE command to the target */\r
- if (Write_Memory_Params.ProgrammingMode & PROG_MODE_COMMIT_PAGE_MASK)\r
- {\r
- SPI_SendByte(Write_Memory_Params.ProgrammingCommands[1]);\r
- SPI_SendByte(StartAddress >> 8);\r
- SPI_SendByte(StartAddress & 0xFF);\r
- SPI_SendByte(0x00);\r
- \r
- /* Check if polling is possible, if not switch to timed delay mode */\r
- if (!(PollAddress))\r
- {\r
- Write_Memory_Params.ProgrammingMode &= ~PROG_MODE_PAGED_VALUE_MASK;\r
- Write_Memory_Params.ProgrammingMode |= PROG_MODE_PAGED_TIMEDELAY_MASK; \r
- }\r
-\r
- ProgrammingStatus = ISPTarget_WaitForProgComplete(Write_Memory_Params.ProgrammingMode, PollAddress, PollValue,\r
- Write_Memory_Params.DelayMS, Write_Memory_Params.ProgrammingCommands[2]);\r
- }\r
- }\r
- else\r
- {\r
- /* Word/byte mode memory programming */\r
- for (uint16_t CurrentByte = 0; CurrentByte < Write_Memory_Params.BytesToWrite; CurrentByte++)\r
- {\r
- bool IsOddByte = (CurrentByte & 0x01);\r
- uint8_t ByteToWrite = *(NextWriteByte++);\r
- \r
- SPI_SendByte(Write_Memory_Params.ProgrammingCommands[0]);\r
- SPI_SendByte(CurrentAddress >> 8);\r
- SPI_SendByte(CurrentAddress & 0xFF);\r
- SPI_SendByte(ByteToWrite);\r
- \r
- /* AVR FLASH addressing requires us to modify the write command based on if we are writing a high\r
- * or low byte at the current word address */\r
- if (V2Command == CMD_PROGRAM_FLASH_ISP)\r
- Write_Memory_Params.ProgrammingCommands[0] ^= READ_WRITE_HIGH_BYTE_MASK;\r
-\r
- if (ByteToWrite != PollValue)\r
- {\r
- if (IsOddByte && (V2Command == CMD_PROGRAM_FLASH_ISP))\r
- Write_Memory_Params.ProgrammingCommands[2] |= READ_WRITE_HIGH_BYTE_MASK;\r
- \r
- PollAddress = (CurrentAddress & 0xFFFF);\r
- }\r
-\r
- if (IsOddByte || (V2Command == CMD_PROGRAM_EEPROM_ISP))\r
- CurrentAddress++;\r
- \r
- ProgrammingStatus = ISPTarget_WaitForProgComplete(Write_Memory_Params.ProgrammingMode, PollAddress, PollValue,\r
- Write_Memory_Params.DelayMS, Write_Memory_Params.ProgrammingCommands[2]);\r
- \r
- if (ProgrammingStatus != STATUS_CMD_OK)\r
- break;\r
- }\r
- }\r
-\r
- Endpoint_Write_Byte(V2Command);\r
- Endpoint_Write_Byte(ProgrammingStatus);\r
- Endpoint_ClearIN();\r
-}\r
-\r
-/** Handler for the CMD_READ_FLASH_ISP and CMD_READ_EEPROM_ISP commands, reading in bytes,\r
- * words or pages of data from the attached device.\r
- *\r
- * \param[in] V2Command Issued V2 Protocol command byte from the host\r
- */\r
-void ISPProtocol_ReadMemory(uint8_t V2Command)\r
-{\r
- struct\r
- {\r
- uint16_t BytesToRead;\r
- uint8_t ReadMemoryCommand;\r
- } Read_Memory_Params;\r
- \r
- Endpoint_Read_Stream_LE(&Read_Memory_Params, sizeof(Read_Memory_Params), NO_STREAM_CALLBACK);\r
- Read_Memory_Params.BytesToRead = SwapEndian_16(Read_Memory_Params.BytesToRead);\r
- \r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
- \r
- Endpoint_Write_Byte(V2Command);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
- \r
- /* Check to see if the host has issued a SET ADDRESS command and we haven't sent a\r
- * LOAD EXTENDED ADDRESS command (if needed, used when operating beyond the 128KB\r
- * FLASH barrier) */\r
- if (MustSetAddress)\r
- {\r
- if (CurrentAddress & (1UL << 31))\r
- ISPTarget_LoadExtendedAddress();\r
-\r
- MustSetAddress = false;\r
- }\r
-\r
- /* Read each byte from the device and write them to the packet for the host */\r
- for (uint16_t CurrentByte = 0; CurrentByte < Read_Memory_Params.BytesToRead; CurrentByte++)\r
- {\r
- /* Read the next byte from the desired memory space in the device */\r
- SPI_SendByte(Read_Memory_Params.ReadMemoryCommand);\r
- SPI_SendByte(CurrentAddress >> 8);\r
- SPI_SendByte(CurrentAddress & 0xFF);\r
- Endpoint_Write_Byte(SPI_ReceiveByte());\r
- \r
- /* Check if the endpoint bank is currently full, if so send the packet */\r
- if (!(Endpoint_IsReadWriteAllowed()))\r
- {\r
- Endpoint_ClearIN();\r
- Endpoint_WaitUntilReady();\r
- }\r
- \r
- /* AVR FLASH addressing requires us to modify the read command based on if we are reading a high\r
- * or low byte at the current word address */\r
- if (V2Command == CMD_READ_FLASH_ISP)\r
- Read_Memory_Params.ReadMemoryCommand ^= READ_WRITE_HIGH_BYTE_MASK;\r
-\r
- /* Only increment the current address if we have read both bytes in the current word when in FLASH\r
- * read mode, or for each byte when in EEPROM read mode */ \r
- if (((CurrentByte & 0x01) && (V2Command == CMD_READ_FLASH_ISP)) || (V2Command == CMD_READ_EEPROM_ISP))\r
- CurrentAddress++;\r
- }\r
-\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
-\r
- bool IsEndpointFull = !(Endpoint_IsReadWriteAllowed());\r
- Endpoint_ClearIN();\r
- \r
- /* Ensure last packet is a short packet to terminate the transfer */\r
- if (IsEndpointFull)\r
- {\r
- Endpoint_WaitUntilReady(); \r
- Endpoint_ClearIN();\r
- Endpoint_WaitUntilReady(); \r
- }\r
-}\r
-\r
-/** Handler for the CMD_CHI_ERASE_ISP command, clearing the target's FLASH memory. */\r
-void ISPProtocol_ChipErase(void)\r
-{\r
- struct\r
- {\r
- uint8_t EraseDelayMS;\r
- uint8_t PollMethod;\r
- uint8_t EraseCommandBytes[4];\r
- } Erase_Chip_Params;\r
- \r
- Endpoint_Read_Stream_LE(&Erase_Chip_Params, sizeof(Erase_Chip_Params), NO_STREAM_CALLBACK);\r
- \r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
- \r
- uint8_t ResponseStatus = STATUS_CMD_OK;\r
- \r
- /* Send the chip erase commands as given by the host to the device */\r
- for (uint8_t SByte = 0; SByte < sizeof(Erase_Chip_Params.EraseCommandBytes); SByte++)\r
- SPI_SendByte(Erase_Chip_Params.EraseCommandBytes[SByte]);\r
-\r
- /* Use appropriate command completion check as given by the host (delay or busy polling) */\r
- if (!(Erase_Chip_Params.PollMethod))\r
- ISPProtocol_DelayMS(Erase_Chip_Params.EraseDelayMS);\r
- else\r
- ResponseStatus = ISPTarget_WaitWhileTargetBusy();\r
- \r
- Endpoint_Write_Byte(CMD_CHIP_ERASE_ISP);\r
- Endpoint_Write_Byte(ResponseStatus);\r
- Endpoint_ClearIN();\r
-}\r
-\r
-/** Handler for the CMD_READ_FUSE_ISP, CMD_READ_LOCK_ISP, CMD_READ_SIGNATURE_ISP and CMD_READ_OSCCAL commands,\r
- * reading the requested configuration byte from the device.\r
- *\r
- * \param[in] V2Command Issued V2 Protocol command byte from the host\r
- */\r
-void ISPProtocol_ReadFuseLockSigOSCCAL(uint8_t V2Command)\r
-{\r
- struct\r
- {\r
- uint8_t RetByte;\r
- uint8_t ReadCommandBytes[4];\r
- } Read_FuseLockSigOSCCAL_Params;\r
- \r
- Endpoint_Read_Stream_LE(&Read_FuseLockSigOSCCAL_Params, sizeof(Read_FuseLockSigOSCCAL_Params), NO_STREAM_CALLBACK);\r
-\r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
-\r
- uint8_t ResponseBytes[4];\r
-\r
- /* Send the Fuse or Lock byte read commands as given by the host to the device, store response */\r
- for (uint8_t RByte = 0; RByte < sizeof(ResponseBytes); RByte++)\r
- ResponseBytes[RByte] = SPI_TransferByte(Read_FuseLockSigOSCCAL_Params.ReadCommandBytes[RByte]);\r
- \r
- Endpoint_Write_Byte(V2Command);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
- Endpoint_Write_Byte(ResponseBytes[Read_FuseLockSigOSCCAL_Params.RetByte - 1]);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
- Endpoint_ClearIN();\r
-}\r
-\r
-/** Handler for the CMD_WRITE_FUSE_ISP and CMD_WRITE_LOCK_ISP commands, writing the requested configuration\r
- * byte to the device.\r
- *\r
- * \param[in] V2Command Issued V2 Protocol command byte from the host\r
- */\r
-void ISPProtocol_WriteFuseLock(uint8_t V2Command)\r
-{\r
- struct\r
- {\r
- uint8_t WriteCommandBytes[4];\r
- } Write_FuseLockSig_Params;\r
- \r
- Endpoint_Read_Stream_LE(&Write_FuseLockSig_Params, sizeof(Write_FuseLockSig_Params), NO_STREAM_CALLBACK);\r
-\r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
-\r
- /* Send the Fuse or Lock byte program commands as given by the host to the device */\r
- for (uint8_t SByte = 0; SByte < sizeof(Write_FuseLockSig_Params.WriteCommandBytes); SByte++)\r
- SPI_SendByte(Write_FuseLockSig_Params.WriteCommandBytes[SByte]);\r
- \r
- Endpoint_Write_Byte(V2Command);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
- Endpoint_ClearIN();\r
-}\r
-\r
-/** Handler for the CMD_SPI_MULTI command, writing and reading arbitrary SPI data to and from the attached device. */\r
-void ISPProtocol_SPIMulti(void)\r
-{\r
- struct\r
- {\r
- uint8_t TxBytes;\r
- uint8_t RxBytes;\r
- uint8_t RxStartAddr;\r
- uint8_t TxData[255];\r
- } SPI_Multi_Params;\r
- \r
- Endpoint_Read_Stream_LE(&SPI_Multi_Params, (sizeof(SPI_Multi_Params) - sizeof(SPI_Multi_Params.TxData)), NO_STREAM_CALLBACK);\r
- Endpoint_Read_Stream_LE(&SPI_Multi_Params.TxData, SPI_Multi_Params.TxBytes, NO_STREAM_CALLBACK);\r
- \r
- Endpoint_ClearOUT();\r
- Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);\r
- \r
- Endpoint_Write_Byte(CMD_SPI_MULTI);\r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
-\r
- uint8_t CurrTxPos = 0;\r
- uint8_t CurrRxPos = 0;\r
-\r
- /* Write out bytes to transmit until the start of the bytes to receive is met */\r
- while (CurrTxPos < SPI_Multi_Params.RxStartAddr)\r
- {\r
- if (CurrTxPos < SPI_Multi_Params.TxBytes)\r
- SPI_SendByte(SPI_Multi_Params.TxData[CurrTxPos]);\r
- else\r
- SPI_SendByte(0);\r
- \r
- CurrTxPos++;\r
- }\r
-\r
- /* Transmit remaining bytes with padding as needed, read in response bytes */\r
- while (CurrRxPos < SPI_Multi_Params.RxBytes)\r
- {\r
- if (CurrTxPos < SPI_Multi_Params.TxBytes)\r
- Endpoint_Write_Byte(SPI_TransferByte(SPI_Multi_Params.TxData[CurrTxPos++]));\r
- else\r
- Endpoint_Write_Byte(SPI_ReceiveByte());\r
- \r
- /* Check to see if we have filled the endpoint bank and need to send the packet */\r
- if (!(Endpoint_IsReadWriteAllowed()))\r
- {\r
- Endpoint_ClearIN();\r
- Endpoint_WaitUntilReady();\r
- }\r
- \r
- CurrRxPos++;\r
- } \r
- \r
- Endpoint_Write_Byte(STATUS_CMD_OK);\r
-\r
- bool IsEndpointFull = !(Endpoint_IsReadWriteAllowed());\r
- Endpoint_ClearIN();\r
- \r
- /* Ensure last packet is a short packet to terminate the transfer */\r
- if (IsEndpointFull)\r
- {\r
- Endpoint_WaitUntilReady(); \r
- Endpoint_ClearIN();\r
- Endpoint_WaitUntilReady(); \r
- }\r
-}\r
-\r
-/** Blocking delay for a given number of milliseconds.\r
- *\r
- * \param[in] DelayMS Number of milliseconds to delay for\r
- */\r
-void ISPProtocol_DelayMS(uint8_t DelayMS)\r
-{\r
- while (DelayMS-- && TimeoutMSRemaining)\r
- {\r
- if (TimeoutMSRemaining)\r
- TimeoutMSRemaining--;\r
- \r
- _delay_ms(1);\r
- }\r
-}\r
-\r
-#endif
\ No newline at end of file
+/*
+ LUFA Library
+ Copyright (C) Dean Camera, 2011.
+
+ dean [at] fourwalledcubicle [dot] com
+ www.lufa-lib.org
+*/
+
+/*
+ Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that the copyright notice and this
+ permission notice and warranty disclaimer appear in supporting
+ documentation, and that the name of the author not be used in
+ advertising or publicity pertaining to distribution of the
+ software without specific, written prior permission.
+
+ The author disclaim all warranties with regard to this
+ software, including all implied warranties of merchantability
+ and fitness. In no event shall the author be liable for any
+ special, indirect or consequential damages or any damages
+ whatsoever resulting from loss of use, data or profits, whether
+ in an action of contract, negligence or other tortious action,
+ arising out of or in connection with the use or performance of
+ this software.
+*/
+
+/** \file
+ *
+ * ISP Protocol handler, to process V2 Protocol wrapped ISP commands used in Atmel programmer devices.
+ */
+
+#include "ISPProtocol.h"
+
+#if defined(ENABLE_ISP_PROTOCOL) || defined(__DOXYGEN__)
+
+/** Handler for the CMD_ENTER_PROGMODE_ISP command, which attempts to enter programming mode on
+ * the attached device, returning success or failure back to the host.
+ */
+void ISPProtocol_EnterISPMode(void)
+{
+ struct
+ {
+ uint8_t TimeoutMS;
+ uint8_t PinStabDelayMS;
+ uint8_t ExecutionDelayMS;
+ uint8_t SynchLoops;
+ uint8_t ByteDelay;
+ uint8_t PollValue;
+ uint8_t PollIndex;
+ uint8_t EnterProgBytes[4];
+ } Enter_ISP_Params;
+
+ Endpoint_Read_Stream_LE(&Enter_ISP_Params, sizeof(Enter_ISP_Params), NULL);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ uint8_t ResponseStatus = STATUS_CMD_FAILED;
+
+ CurrentAddress = 0;
+
+ /* Perform execution delay, initialize SPI bus */
+ ISPProtocol_DelayMS(Enter_ISP_Params.ExecutionDelayMS);
+ ISPTarget_EnableTargetISP();
+
+ ISPTarget_ChangeTargetResetLine(true);
+
+ /* Continuously attempt to synchronize with the target until either the number of attempts specified
+ * by the host has exceeded, or the the device sends back the expected response values */
+ while (Enter_ISP_Params.SynchLoops-- && (ResponseStatus != STATUS_CMD_OK) && !(TimeoutExpired))
+ {
+ uint8_t ResponseBytes[4];
+
+ for (uint8_t RByte = 0; RByte < sizeof(ResponseBytes); RByte++)
+ {
+ ISPProtocol_DelayMS(Enter_ISP_Params.ByteDelay);
+ ResponseBytes[RByte] = ISPTarget_TransferByte(Enter_ISP_Params.EnterProgBytes[RByte]);
+ }
+
+ /* Check if polling disabled, or if the polled value matches the expected value */
+ if (!(Enter_ISP_Params.PollIndex) || (ResponseBytes[Enter_ISP_Params.PollIndex - 1] == Enter_ISP_Params.PollValue))
+ {
+ ResponseStatus = STATUS_CMD_OK;
+ }
+ else
+ {
+ ISPTarget_ChangeTargetResetLine(false);
+ ISPProtocol_DelayMS(Enter_ISP_Params.PinStabDelayMS);
+ ISPTarget_ChangeTargetResetLine(true);
+ }
+ }
+
+ Endpoint_Write_8(CMD_ENTER_PROGMODE_ISP);
+ Endpoint_Write_8(ResponseStatus);
+ Endpoint_ClearIN();
+}
+
+/** Handler for the CMD_LEAVE_ISP command, which releases the target from programming mode. */
+void ISPProtocol_LeaveISPMode(void)
+{
+ struct
+ {
+ uint8_t PreDelayMS;
+ uint8_t PostDelayMS;
+ } Leave_ISP_Params;
+
+ Endpoint_Read_Stream_LE(&Leave_ISP_Params, sizeof(Leave_ISP_Params), NULL);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ /* Perform pre-exit delay, release the target /RESET, disable the SPI bus and perform the post-exit delay */
+ ISPProtocol_DelayMS(Leave_ISP_Params.PreDelayMS);
+ ISPTarget_ChangeTargetResetLine(false);
+ ISPTarget_DisableTargetISP();
+ ISPProtocol_DelayMS(Leave_ISP_Params.PostDelayMS);
+
+ Endpoint_Write_8(CMD_LEAVE_PROGMODE_ISP);
+ Endpoint_Write_8(STATUS_CMD_OK);
+ Endpoint_ClearIN();
+}
+
+/** Handler for the CMD_PROGRAM_FLASH_ISP and CMD_PROGRAM_EEPROM_ISP commands, writing out bytes,
+ * words or pages of data to the attached device.
+ *
+ * \param[in] V2Command Issued V2 Protocol command byte from the host
+ */
+void ISPProtocol_ProgramMemory(uint8_t V2Command)
+{
+ struct
+ {
+ uint16_t BytesToWrite;
+ uint8_t ProgrammingMode;
+ uint8_t DelayMS;
+ uint8_t ProgrammingCommands[3];
+ uint8_t PollValue1;
+ uint8_t PollValue2;
+ uint8_t ProgData[256]; // Note, the Jungo driver has a very short ACK timeout period, need to buffer the
+ } Write_Memory_Params; // whole page and ACK the packet as fast as possible to prevent it from aborting
+
+ Endpoint_Read_Stream_LE(&Write_Memory_Params, (sizeof(Write_Memory_Params) -
+ sizeof(Write_Memory_Params.ProgData)), NULL);
+ Write_Memory_Params.BytesToWrite = SwapEndian_16(Write_Memory_Params.BytesToWrite);
+
+ if (Write_Memory_Params.BytesToWrite > sizeof(Write_Memory_Params.ProgData))
+ {
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ Endpoint_Write_8(V2Command);
+ Endpoint_Write_8(STATUS_CMD_FAILED);
+ Endpoint_ClearIN();
+ return;
+ }
+
+ Endpoint_Read_Stream_LE(&Write_Memory_Params.ProgData, Write_Memory_Params.BytesToWrite, NULL);
+
+ // The driver will terminate transfers that are a round multiple of the endpoint bank in size with a ZLP, need
+ // to catch this and discard it before continuing on with packet processing to prevent communication issues
+ if (((sizeof(uint8_t) + sizeof(Write_Memory_Params) - sizeof(Write_Memory_Params.ProgData)) +
+ Write_Memory_Params.BytesToWrite) % AVRISP_DATA_EPSIZE == 0)
+ {
+ Endpoint_ClearOUT();
+ Endpoint_WaitUntilReady();
+ }
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ uint8_t ProgrammingStatus = STATUS_CMD_OK;
+ uint8_t PollValue = (V2Command == CMD_PROGRAM_FLASH_ISP) ? Write_Memory_Params.PollValue1 :
+ Write_Memory_Params.PollValue2;
+ uint16_t PollAddress = 0;
+ uint8_t* NextWriteByte = Write_Memory_Params.ProgData;
+ uint16_t PageStartAddress = (CurrentAddress & 0xFFFF);
+
+ for (uint16_t CurrentByte = 0; CurrentByte < Write_Memory_Params.BytesToWrite; CurrentByte++)
+ {
+ uint8_t ByteToWrite = *(NextWriteByte++);
+ uint8_t ProgrammingMode = Write_Memory_Params.ProgrammingMode;
+
+ /* Check to see if we need to send a LOAD EXTENDED ADDRESS command to the target */
+ if (MustLoadExtendedAddress)
+ {
+ ISPTarget_LoadExtendedAddress();
+ MustLoadExtendedAddress = false;
+ }
+
+ ISPTarget_SendByte(Write_Memory_Params.ProgrammingCommands[0]);
+ ISPTarget_SendByte(CurrentAddress >> 8);
+ ISPTarget_SendByte(CurrentAddress & 0xFF);
+ ISPTarget_SendByte(ByteToWrite);
+
+ /* AVR FLASH addressing requires us to modify the write command based on if we are writing a high
+ * or low byte at the current word address */
+ if (V2Command == CMD_PROGRAM_FLASH_ISP)
+ Write_Memory_Params.ProgrammingCommands[0] ^= READ_WRITE_HIGH_BYTE_MASK;
+
+ /* Check to see if we have a valid polling address */
+ if (!(PollAddress) && (ByteToWrite != PollValue))
+ {
+ if ((CurrentByte & 0x01) && (V2Command == CMD_PROGRAM_FLASH_ISP))
+ Write_Memory_Params.ProgrammingCommands[2] |= READ_WRITE_HIGH_BYTE_MASK;
+ else
+ Write_Memory_Params.ProgrammingCommands[2] &= ~READ_WRITE_HIGH_BYTE_MASK;
+
+ PollAddress = (CurrentAddress & 0xFFFF);
+ }
+
+ /* If in word programming mode, commit the byte to the target's memory */
+ if (!(ProgrammingMode & PROG_MODE_PAGED_WRITES_MASK))
+ {
+ /* If the current polling address is invalid, switch to timed delay write completion mode */
+ if (!(PollAddress) && !(ProgrammingMode & PROG_MODE_WORD_READYBUSY_MASK))
+ ProgrammingMode = (ProgrammingMode & ~PROG_MODE_WORD_VALUE_MASK) | PROG_MODE_WORD_TIMEDELAY_MASK;
+
+ ProgrammingStatus = ISPTarget_WaitForProgComplete(ProgrammingMode, PollAddress, PollValue,
+ Write_Memory_Params.DelayMS,
+ Write_Memory_Params.ProgrammingCommands[2]);
+
+ /* Abort the programming loop early if the byte/word programming failed */
+ if (ProgrammingStatus != STATUS_CMD_OK)
+ break;
+
+ /* Must reset the polling address afterwards, so it is not erroneously used for the next byte */
+ PollAddress = 0;
+ }
+
+ /* EEPROM just increments the address each byte, flash needs to increment on each word and
+ * also check to ensure that a LOAD EXTENDED ADDRESS command is issued each time the extended
+ * address boundary has been crossed during FLASH memory programming */
+ if ((CurrentByte & 0x01) || (V2Command == CMD_PROGRAM_EEPROM_ISP))
+ {
+ CurrentAddress++;
+
+ if ((V2Command == CMD_PROGRAM_FLASH_ISP) && !(CurrentAddress & 0xFFFF))
+ MustLoadExtendedAddress = true;
+ }
+ }
+
+ /* If the current page must be committed, send the PROGRAM PAGE command to the target */
+ if (Write_Memory_Params.ProgrammingMode & PROG_MODE_COMMIT_PAGE_MASK)
+ {
+ ISPTarget_SendByte(Write_Memory_Params.ProgrammingCommands[1]);
+ ISPTarget_SendByte(PageStartAddress >> 8);
+ ISPTarget_SendByte(PageStartAddress & 0xFF);
+ ISPTarget_SendByte(0x00);
+
+ /* Check if polling is enabled and possible, if not switch to timed delay mode */
+ if ((Write_Memory_Params.ProgrammingMode & PROG_MODE_PAGED_VALUE_MASK) && !(PollAddress))
+ {
+ Write_Memory_Params.ProgrammingMode = (Write_Memory_Params.ProgrammingMode & ~PROG_MODE_PAGED_VALUE_MASK) |
+ PROG_MODE_PAGED_TIMEDELAY_MASK;
+ }
+
+ ProgrammingStatus = ISPTarget_WaitForProgComplete(Write_Memory_Params.ProgrammingMode, PollAddress, PollValue,
+ Write_Memory_Params.DelayMS,
+ Write_Memory_Params.ProgrammingCommands[2]);
+
+ /* Check to see if the FLASH address has crossed the extended address boundary */
+ if ((V2Command == CMD_PROGRAM_FLASH_ISP) && !(CurrentAddress & 0xFFFF))
+ MustLoadExtendedAddress = true;
+ }
+
+ Endpoint_Write_8(V2Command);
+ Endpoint_Write_8(ProgrammingStatus);
+ Endpoint_ClearIN();
+}
+
+/** Handler for the CMD_READ_FLASH_ISP and CMD_READ_EEPROM_ISP commands, reading in bytes,
+ * words or pages of data from the attached device.
+ *
+ * \param[in] V2Command Issued V2 Protocol command byte from the host
+ */
+void ISPProtocol_ReadMemory(uint8_t V2Command)
+{
+ struct
+ {
+ uint16_t BytesToRead;
+ uint8_t ReadMemoryCommand;
+ } Read_Memory_Params;
+
+ Endpoint_Read_Stream_LE(&Read_Memory_Params, sizeof(Read_Memory_Params), NULL);
+ Read_Memory_Params.BytesToRead = SwapEndian_16(Read_Memory_Params.BytesToRead);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ Endpoint_Write_8(V2Command);
+ Endpoint_Write_8(STATUS_CMD_OK);
+
+ /* Read each byte from the device and write them to the packet for the host */
+ for (uint16_t CurrentByte = 0; CurrentByte < Read_Memory_Params.BytesToRead; CurrentByte++)
+ {
+ /* Check to see if we need to send a LOAD EXTENDED ADDRESS command to the target */
+ if (MustLoadExtendedAddress)
+ {
+ ISPTarget_LoadExtendedAddress();
+ MustLoadExtendedAddress = false;
+ }
+
+ /* Read the next byte from the desired memory space in the device */
+ ISPTarget_SendByte(Read_Memory_Params.ReadMemoryCommand);
+ ISPTarget_SendByte(CurrentAddress >> 8);
+ ISPTarget_SendByte(CurrentAddress & 0xFF);
+ Endpoint_Write_8(ISPTarget_ReceiveByte());
+
+ /* Check if the endpoint bank is currently full, if so send the packet */
+ if (!(Endpoint_IsReadWriteAllowed()))
+ {
+ Endpoint_ClearIN();
+ Endpoint_WaitUntilReady();
+ }
+
+ /* AVR FLASH addressing requires us to modify the read command based on if we are reading a high
+ * or low byte at the current word address */
+ if (V2Command == CMD_READ_FLASH_ISP)
+ Read_Memory_Params.ReadMemoryCommand ^= READ_WRITE_HIGH_BYTE_MASK;
+
+ /* EEPROM just increments the address each byte, flash needs to increment on each word and
+ * also check to ensure that a LOAD EXTENDED ADDRESS command is issued each time the extended
+ * address boundary has been crossed */
+ if ((CurrentByte & 0x01) || (V2Command == CMD_READ_EEPROM_ISP))
+ {
+ CurrentAddress++;
+
+ if ((V2Command != CMD_READ_EEPROM_ISP) && !(CurrentAddress & 0xFFFF))
+ MustLoadExtendedAddress = true;
+ }
+ }
+
+ Endpoint_Write_8(STATUS_CMD_OK);
+
+ bool IsEndpointFull = !(Endpoint_IsReadWriteAllowed());
+ Endpoint_ClearIN();
+
+ /* Ensure last packet is a short packet to terminate the transfer */
+ if (IsEndpointFull)
+ {
+ Endpoint_WaitUntilReady();
+ Endpoint_ClearIN();
+ Endpoint_WaitUntilReady();
+ }
+}
+
+/** Handler for the CMD_CHI_ERASE_ISP command, clearing the target's FLASH memory. */
+void ISPProtocol_ChipErase(void)
+{
+ struct
+ {
+ uint8_t EraseDelayMS;
+ uint8_t PollMethod;
+ uint8_t EraseCommandBytes[4];
+ } Erase_Chip_Params;
+
+ Endpoint_Read_Stream_LE(&Erase_Chip_Params, sizeof(Erase_Chip_Params), NULL);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ uint8_t ResponseStatus = STATUS_CMD_OK;
+
+ /* Send the chip erase commands as given by the host to the device */
+ for (uint8_t SByte = 0; SByte < sizeof(Erase_Chip_Params.EraseCommandBytes); SByte++)
+ ISPTarget_SendByte(Erase_Chip_Params.EraseCommandBytes[SByte]);
+
+ /* Use appropriate command completion check as given by the host (delay or busy polling) */
+ if (!(Erase_Chip_Params.PollMethod))
+ ISPProtocol_DelayMS(Erase_Chip_Params.EraseDelayMS);
+ else
+ ResponseStatus = ISPTarget_WaitWhileTargetBusy();
+
+ Endpoint_Write_8(CMD_CHIP_ERASE_ISP);
+ Endpoint_Write_8(ResponseStatus);
+ Endpoint_ClearIN();
+}
+
+/** Handler for the CMD_READ_FUSE_ISP, CMD_READ_LOCK_ISP, CMD_READ_SIGNATURE_ISP and CMD_READ_OSCCAL commands,
+ * reading the requested configuration byte from the device.
+ *
+ * \param[in] V2Command Issued V2 Protocol command byte from the host
+ */
+void ISPProtocol_ReadFuseLockSigOSCCAL(uint8_t V2Command)
+{
+ struct
+ {
+ uint8_t RetByte;
+ uint8_t ReadCommandBytes[4];
+ } Read_FuseLockSigOSCCAL_Params;
+
+ Endpoint_Read_Stream_LE(&Read_FuseLockSigOSCCAL_Params, sizeof(Read_FuseLockSigOSCCAL_Params), NULL);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ uint8_t ResponseBytes[4];
+
+ /* Send the Fuse or Lock byte read commands as given by the host to the device, store response */
+ for (uint8_t RByte = 0; RByte < sizeof(ResponseBytes); RByte++)
+ ResponseBytes[RByte] = ISPTarget_TransferByte(Read_FuseLockSigOSCCAL_Params.ReadCommandBytes[RByte]);
+
+ Endpoint_Write_8(V2Command);
+ Endpoint_Write_8(STATUS_CMD_OK);
+ Endpoint_Write_8(ResponseBytes[Read_FuseLockSigOSCCAL_Params.RetByte - 1]);
+ Endpoint_Write_8(STATUS_CMD_OK);
+ Endpoint_ClearIN();
+}
+
+/** Handler for the CMD_WRITE_FUSE_ISP and CMD_WRITE_LOCK_ISP commands, writing the requested configuration
+ * byte to the device.
+ *
+ * \param[in] V2Command Issued V2 Protocol command byte from the host
+ */
+void ISPProtocol_WriteFuseLock(uint8_t V2Command)
+{
+ struct
+ {
+ uint8_t WriteCommandBytes[4];
+ } Write_FuseLockSig_Params;
+
+ Endpoint_Read_Stream_LE(&Write_FuseLockSig_Params, sizeof(Write_FuseLockSig_Params), NULL);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ /* Send the Fuse or Lock byte program commands as given by the host to the device */
+ for (uint8_t SByte = 0; SByte < sizeof(Write_FuseLockSig_Params.WriteCommandBytes); SByte++)
+ ISPTarget_SendByte(Write_FuseLockSig_Params.WriteCommandBytes[SByte]);
+
+ Endpoint_Write_8(V2Command);
+ Endpoint_Write_8(STATUS_CMD_OK);
+ Endpoint_Write_8(STATUS_CMD_OK);
+ Endpoint_ClearIN();
+}
+
+/** Handler for the CMD_SPI_MULTI command, writing and reading arbitrary SPI data to and from the attached device. */
+void ISPProtocol_SPIMulti(void)
+{
+ struct
+ {
+ uint8_t TxBytes;
+ uint8_t RxBytes;
+ uint8_t RxStartAddr;
+ uint8_t TxData[255];
+ } SPI_Multi_Params;
+
+ Endpoint_Read_Stream_LE(&SPI_Multi_Params, (sizeof(SPI_Multi_Params) - sizeof(SPI_Multi_Params.TxData)), NULL);
+ Endpoint_Read_Stream_LE(&SPI_Multi_Params.TxData, SPI_Multi_Params.TxBytes, NULL);
+
+ Endpoint_ClearOUT();
+ Endpoint_SelectEndpoint(AVRISP_DATA_IN_EPNUM);
+ Endpoint_SetEndpointDirection(ENDPOINT_DIR_IN);
+
+ Endpoint_Write_8(CMD_SPI_MULTI);
+ Endpoint_Write_8(STATUS_CMD_OK);
+
+ uint8_t CurrTxPos = 0;
+ uint8_t CurrRxPos = 0;
+
+ /* Write out bytes to transmit until the start of the bytes to receive is met */
+ while (CurrTxPos < SPI_Multi_Params.RxStartAddr)
+ {
+ if (CurrTxPos < SPI_Multi_Params.TxBytes)
+ ISPTarget_SendByte(SPI_Multi_Params.TxData[CurrTxPos]);
+ else
+ ISPTarget_SendByte(0);
+
+ CurrTxPos++;
+ }
+
+ /* Transmit remaining bytes with padding as needed, read in response bytes */
+ while (CurrRxPos < SPI_Multi_Params.RxBytes)
+ {
+ if (CurrTxPos < SPI_Multi_Params.TxBytes)
+ Endpoint_Write_8(ISPTarget_TransferByte(SPI_Multi_Params.TxData[CurrTxPos++]));
+ else
+ Endpoint_Write_8(ISPTarget_ReceiveByte());
+
+ /* Check to see if we have filled the endpoint bank and need to send the packet */
+ if (!(Endpoint_IsReadWriteAllowed()))
+ {
+ Endpoint_ClearIN();
+ Endpoint_WaitUntilReady();
+ }
+
+ CurrRxPos++;
+ }
+
+ Endpoint_Write_8(STATUS_CMD_OK);
+
+ bool IsEndpointFull = !(Endpoint_IsReadWriteAllowed());
+ Endpoint_ClearIN();
+
+ /* Ensure last packet is a short packet to terminate the transfer */
+ if (IsEndpointFull)
+ {
+ Endpoint_WaitUntilReady();
+ Endpoint_ClearIN();
+ Endpoint_WaitUntilReady();
+ }
+}
+
+/** Blocking delay for a given number of milliseconds.
+ *
+ * \param[in] DelayMS Number of milliseconds to delay for
+ */
+void ISPProtocol_DelayMS(uint8_t DelayMS)
+{
+ while (DelayMS-- && !(TimeoutExpired))
+ Delay_MS(1);
+}
+
+#endif