|
@@ -0,0 +1,308 @@
|
|
|
+// Based on the OptiBoot project
|
|
|
+// https://github.com/Optiboot/optiboot
|
|
|
+// Licence GLP 2 or later.
|
|
|
+
|
|
|
+#include "Marlin.h"
|
|
|
+#include "w25x20cl.h"
|
|
|
+#include "stk500.h"
|
|
|
+
|
|
|
+#define OPTIBOOT_MAJVER 6
|
|
|
+#define OPTIBOOT_CUSTOMVER 0
|
|
|
+#define OPTIBOOT_MINVER 2
|
|
|
+static unsigned const int __attribute__((section(".version")))
|
|
|
+ optiboot_version = 256*(OPTIBOOT_MAJVER + OPTIBOOT_CUSTOMVER) + OPTIBOOT_MINVER;
|
|
|
+
|
|
|
+/* Watchdog settings */
|
|
|
+#define WATCHDOG_OFF (0)
|
|
|
+#define WATCHDOG_16MS (_BV(WDE))
|
|
|
+#define WATCHDOG_32MS (_BV(WDP0) | _BV(WDE))
|
|
|
+#define WATCHDOG_64MS (_BV(WDP1) | _BV(WDE))
|
|
|
+#define WATCHDOG_125MS (_BV(WDP1) | _BV(WDP0) | _BV(WDE))
|
|
|
+#define WATCHDOG_250MS (_BV(WDP2) | _BV(WDE))
|
|
|
+#define WATCHDOG_500MS (_BV(WDP2) | _BV(WDP0) | _BV(WDE))
|
|
|
+#define WATCHDOG_1S (_BV(WDP2) | _BV(WDP1) | _BV(WDE))
|
|
|
+#define WATCHDOG_2S (_BV(WDP2) | _BV(WDP1) | _BV(WDP0) | _BV(WDE))
|
|
|
+#define WATCHDOG_4S (_BV(WDP3) | _BV(WDE))
|
|
|
+#define WATCHDOG_8S (_BV(WDP3) | _BV(WDP0) | _BV(WDE))
|
|
|
+
|
|
|
+#if 0
|
|
|
+#define W25X20CL_SIGNATURE_0 9
|
|
|
+#define W25X20CL_SIGNATURE_1 8
|
|
|
+#define W25X20CL_SIGNATURE_2 7
|
|
|
+#else
|
|
|
+//FIXME this is a signature of ATmega2560!
|
|
|
+#define W25X20CL_SIGNATURE_0 0x1E
|
|
|
+#define W25X20CL_SIGNATURE_1 0x98
|
|
|
+#define W25X20CL_SIGNATURE_2 0x01
|
|
|
+#endif
|
|
|
+
|
|
|
+static void watchdogConfig(uint8_t x) {
|
|
|
+ WDTCSR = _BV(WDCE) | _BV(WDE);
|
|
|
+ WDTCSR = x;
|
|
|
+}
|
|
|
+
|
|
|
+static void watchdogReset() {
|
|
|
+ __asm__ __volatile__ (
|
|
|
+ "wdr\n"
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+#define RECV_READY ((UCSR0A & _BV(RXC0)) != 0)
|
|
|
+
|
|
|
+static uint8_t getch(void) {
|
|
|
+ uint8_t ch;
|
|
|
+ while(! RECV_READY) ;
|
|
|
+ if (!(UCSR0A & _BV(FE0))) {
|
|
|
+ /*
|
|
|
+ * A Framing Error indicates (probably) that something is talking
|
|
|
+ * to us at the wrong bit rate. Assume that this is because it
|
|
|
+ * expects to be talking to the application, and DON'T reset the
|
|
|
+ * watchdog. This should cause the bootloader to abort and run
|
|
|
+ * the application "soon", if it keeps happening. (Note that we
|
|
|
+ * don't care that an invalid char is returned...)
|
|
|
+ */
|
|
|
+ watchdogReset();
|
|
|
+ }
|
|
|
+ ch = UDR0;
|
|
|
+ return ch;
|
|
|
+}
|
|
|
+
|
|
|
+static void putch(char ch) {
|
|
|
+ while (!(UCSR0A & _BV(UDRE0)));
|
|
|
+ UDR0 = ch;
|
|
|
+}
|
|
|
+
|
|
|
+static void verifySpace() {
|
|
|
+ if (getch() != CRC_EOP) {
|
|
|
+ putch(STK_FAILED);
|
|
|
+ watchdogConfig(WATCHDOG_16MS); // shorten WD timeout
|
|
|
+ while (1) // and busy-loop so that WD causes
|
|
|
+ ; // a reset and app start.
|
|
|
+ }
|
|
|
+ putch(STK_INSYNC);
|
|
|
+}
|
|
|
+
|
|
|
+static void getNch(uint8_t count) {
|
|
|
+ do getch(); while (--count);
|
|
|
+ verifySpace();
|
|
|
+}
|
|
|
+
|
|
|
+typedef uint16_t pagelen_t;
|
|
|
+
|
|
|
+static const char entry_magic_send [] PROGMEM = "start\n";
|
|
|
+static const char entry_magic_receive[] PROGMEM = "w25x20cl_enter\n";
|
|
|
+static const char entry_magic_cfm [] PROGMEM = "w25x20cl_cfm\n";
|
|
|
+
|
|
|
+struct block_t;
|
|
|
+extern struct block_t *block_buffer;
|
|
|
+
|
|
|
+void optiboot_w25x20cl_enter()
|
|
|
+{
|
|
|
+ uint8_t ch;
|
|
|
+ uint8_t rampz = 0;
|
|
|
+ register uint16_t address = 0;
|
|
|
+ register pagelen_t length;
|
|
|
+ // Use the planner's queue for the receive / transmit buffers.
|
|
|
+// uint8_t *buff = (uint8_t*)block_buffer;
|
|
|
+ uint8_t buff[260];
|
|
|
+ // bitmap of pages to be written. Bit is set to 1 if the page has already been erased.
|
|
|
+ uint8_t pages_erased = 0;
|
|
|
+
|
|
|
+ // Handshake sequence: Initialize the serial line, flush serial line, send magic, receive magic.
|
|
|
+ // If the magic is not received on time, or it is not received correctly, continue to the application.
|
|
|
+ {
|
|
|
+ watchdogReset();
|
|
|
+ unsigned long boot_timeout = 2000000;
|
|
|
+ unsigned long boot_timer = 0;
|
|
|
+ const char *ptr = entry_magic_send;
|
|
|
+ const char *end = strlen_P(entry_magic_send) + ptr;
|
|
|
+ // Initialize the serial line.
|
|
|
+ UCSR0A |= (1 << U2X0);
|
|
|
+ UBRR0L = (((float)(F_CPU))/(((float)(115200))*8.0)-1.0+0.5);
|
|
|
+ UCSR0B = (1 << RXEN0) | (1 << TXEN0);
|
|
|
+ // Flush the serial line.
|
|
|
+ while (RECV_READY) {
|
|
|
+ watchdogReset();
|
|
|
+ // Dummy register read (discard)
|
|
|
+ (void)(*(char *)UDR0);
|
|
|
+ }
|
|
|
+ // Send the initial magic string.
|
|
|
+ while (ptr != end)
|
|
|
+ putch(pgm_read_byte_far(ptr ++));
|
|
|
+ watchdogReset();
|
|
|
+ // Wait for one second until a magic string (constant entry_magic) is received
|
|
|
+ // from the serial line.
|
|
|
+ ptr = entry_magic_receive;
|
|
|
+ end = strlen_P(entry_magic_receive) + ptr;
|
|
|
+ while (ptr != end) {
|
|
|
+ while (! RECV_READY) {
|
|
|
+ watchdogReset();
|
|
|
+ delayMicroseconds(1);
|
|
|
+ if (++ boot_timer > boot_timeout)
|
|
|
+ // Timeout expired, continue with the application.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ch = UDR0;
|
|
|
+ if (pgm_read_byte_far(ptr ++) != ch)
|
|
|
+ // Magic was not received correctly, continue with the application
|
|
|
+ return;
|
|
|
+ watchdogReset();
|
|
|
+ }
|
|
|
+ // Send the cfm magic string.
|
|
|
+ ptr = entry_magic_cfm;
|
|
|
+ while (ptr != end)
|
|
|
+ putch(pgm_read_byte_far(ptr ++));
|
|
|
+ }
|
|
|
+
|
|
|
+ spi_init();
|
|
|
+ w25x20cl_init();
|
|
|
+ watchdogConfig(WATCHDOG_OFF);
|
|
|
+
|
|
|
+ /* Forever loop: exits by causing WDT reset */
|
|
|
+ for (;;) {
|
|
|
+ /* get character from UART */
|
|
|
+ ch = getch();
|
|
|
+
|
|
|
+ if(ch == STK_GET_PARAMETER) {
|
|
|
+ unsigned char which = getch();
|
|
|
+ verifySpace();
|
|
|
+ /*
|
|
|
+ * Send optiboot version as "SW version"
|
|
|
+ * Note that the references to memory are optimized away.
|
|
|
+ */
|
|
|
+ if (which == STK_SW_MINOR) {
|
|
|
+ putch(optiboot_version & 0xFF);
|
|
|
+ } else if (which == STK_SW_MAJOR) {
|
|
|
+ putch(optiboot_version >> 8);
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * GET PARAMETER returns a generic 0x03 reply for
|
|
|
+ * other parameters - enough to keep Avrdude happy
|
|
|
+ */
|
|
|
+ putch(0x03);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if(ch == STK_SET_DEVICE) {
|
|
|
+ // SET DEVICE is ignored
|
|
|
+ getNch(20);
|
|
|
+ }
|
|
|
+ else if(ch == STK_SET_DEVICE_EXT) {
|
|
|
+ // SET DEVICE EXT is ignored
|
|
|
+ getNch(5);
|
|
|
+ }
|
|
|
+ else if(ch == STK_LOAD_ADDRESS) {
|
|
|
+ // LOAD ADDRESS
|
|
|
+ uint16_t newAddress;
|
|
|
+ // Workaround for the infamous ';' bug in the Prusa3D usb to serial converter.
|
|
|
+ // Send the binary data by nibbles to avoid transmitting the ';' character.
|
|
|
+ newAddress = getch();
|
|
|
+ newAddress |= getch();
|
|
|
+ newAddress |= (((uint16_t)getch()) << 8);
|
|
|
+ newAddress |= (((uint16_t)getch()) << 8);
|
|
|
+ // Transfer top bit to LSB in rampz
|
|
|
+ if (newAddress & 0x8000)
|
|
|
+ rampz |= 0x01;
|
|
|
+ else
|
|
|
+ rampz &= 0xFE;
|
|
|
+ newAddress += newAddress; // Convert from word address to byte address
|
|
|
+ address = newAddress;
|
|
|
+ verifySpace();
|
|
|
+ }
|
|
|
+ else if(ch == STK_UNIVERSAL) {
|
|
|
+ // LOAD_EXTENDED_ADDRESS is needed in STK_UNIVERSAL for addressing more than 128kB
|
|
|
+ if ( AVR_OP_LOAD_EXT_ADDR == getch() ) {
|
|
|
+ // get address
|
|
|
+ getch(); // get '0'
|
|
|
+ rampz = (rampz & 0x01) | ((getch() << 1) & 0xff); // get address and put it in rampz
|
|
|
+ getNch(1); // get last '0'
|
|
|
+ // response
|
|
|
+ putch(0x00);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // everything else is ignored
|
|
|
+ getNch(3);
|
|
|
+ putch(0x00);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Write memory, length is big endian and is in bytes */
|
|
|
+ else if(ch == STK_PROG_PAGE) {
|
|
|
+ // PROGRAM PAGE - we support flash programming only, not EEPROM
|
|
|
+ uint8_t desttype;
|
|
|
+ uint8_t *bufPtr;
|
|
|
+ pagelen_t savelength;
|
|
|
+ // Read the page length, with the length transferred each nibble separately to work around
|
|
|
+ // the Prusa's USB to serial infamous semicolon issue.
|
|
|
+ length = ((pagelen_t)getch()) << 8;
|
|
|
+ length |= ((pagelen_t)getch()) << 8;
|
|
|
+ length |= getch();
|
|
|
+ length |= getch();
|
|
|
+
|
|
|
+ savelength = length;
|
|
|
+ // Read the destination type. It should always be 'F' as flash.
|
|
|
+ desttype = getch();
|
|
|
+
|
|
|
+ // read a page worth of contents
|
|
|
+ bufPtr = buff;
|
|
|
+ do *bufPtr++ = getch();
|
|
|
+ while (--length);
|
|
|
+
|
|
|
+ // Read command terminator, start reply
|
|
|
+ verifySpace();
|
|
|
+ if (desttype == 'E') {
|
|
|
+ while (1) ; // Error: wait for WDT
|
|
|
+ } else {
|
|
|
+ uint32_t addr = (((uint32_t)rampz) << 16) | address;
|
|
|
+ // During a single bootloader run, only erase a 64kB block once.
|
|
|
+ // An 8bit bitmask 'pages_erased' covers 512kB of FLASH memory.
|
|
|
+ if (address == 0 && (pages_erased & (1 << addr)) == 0) {
|
|
|
+ w25x20cl_wait_busy();
|
|
|
+ w25x20cl_enable_wr();
|
|
|
+ w25x20cl_block64_erase(addr);
|
|
|
+ pages_erased |= (1 << addr);
|
|
|
+ }
|
|
|
+ w25x20cl_wait_busy();
|
|
|
+ w25x20cl_enable_wr();
|
|
|
+ w25x20cl_page_program(addr, buff, savelength);
|
|
|
+ w25x20cl_wait_busy();
|
|
|
+ w25x20cl_disable_wr();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Read memory block mode, length is big endian. */
|
|
|
+ else if(ch == STK_READ_PAGE) {
|
|
|
+ uint32_t addr = (((uint32_t)rampz) << 16) | address;
|
|
|
+ uint8_t desttype;
|
|
|
+ register pagelen_t i;
|
|
|
+ // Read the page length, with the length transferred each nibble separately to work around
|
|
|
+ // the Prusa's USB to serial infamous semicolon issue.
|
|
|
+ length = ((pagelen_t)getch()) << 8;
|
|
|
+ length |= ((pagelen_t)getch()) << 8;
|
|
|
+ length |= getch();
|
|
|
+ length |= getch();
|
|
|
+ // Read the destination type. It should always be 'F' as flash.
|
|
|
+ desttype = getch();
|
|
|
+ verifySpace();
|
|
|
+ w25x20cl_wait_busy();
|
|
|
+ w25x20cl_rd_data(addr, buff, length);
|
|
|
+ for (i = 0; i < length; ++ i)
|
|
|
+ putch(buff[i]);
|
|
|
+ }
|
|
|
+ /* Get device signature bytes */
|
|
|
+ else if(ch == STK_READ_SIGN) {
|
|
|
+ // READ SIGN - return what Avrdude wants to hear
|
|
|
+ verifySpace();
|
|
|
+ putch(W25X20CL_SIGNATURE_0);
|
|
|
+ putch(W25X20CL_SIGNATURE_1);
|
|
|
+ putch(W25X20CL_SIGNATURE_2);
|
|
|
+ }
|
|
|
+ else if (ch == STK_LEAVE_PROGMODE) { /* 'Q' */
|
|
|
+ // Adaboot no-wait mod
|
|
|
+ watchdogConfig(WATCHDOG_16MS);
|
|
|
+ verifySpace();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // This covers the response to commands like STK_ENTER_PROGMODE
|
|
|
+ verifySpace();
|
|
|
+ }
|
|
|
+ putch(STK_OK);
|
|
|
+ }
|
|
|
+}
|