From b6e72dce4cc3604e38ff6469f4b48f1872cddbb3 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Wed, 8 Dec 2021 10:09:52 +0000
Subject: [PATCH 01/12] Implemented basic serial code refactoring for upcoming
 SerialUPDI implementation

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serial_refactoring@1511 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 arduino.c    |  3 ++-
 avr910.c     |  3 ++-
 buspirate.c  |  3 ++-
 butterfly.c  |  3 ++-
 jtagmkI.c    |  9 +++++----
 jtagmkII.c   | 23 +++++++++++++++--------
 libavrdude.h | 31 ++++++++++++++++++++++++++++---
 ser_posix.c  | 28 +++++++++++++++++++++-------
 ser_win32.c  | 44 ++++++++++++++++++++++++++++++++++++++------
 stk500.c     |  3 ++-
 stk500v2.c   | 17 ++++++++++-------
 wiring.c     |  3 ++-
 xbee.c       | 12 +++++++-----
 13 files changed, 136 insertions(+), 46 deletions(-)

diff --git a/arduino.c b/arduino.c
index 566f56ab..dbaafef2 100644
--- a/arduino.c
+++ b/arduino.c
@@ -84,7 +84,8 @@ static int arduino_open(PROGRAMMER * pgm, char * port)
 {
   union pinfo pinfo;
   strcpy(pgm->port, port);
-  pinfo.baud = pgm->baudrate? pgm->baudrate: 115200;
+  pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
   if (serial_open(port, pinfo, &pgm->fd)==-1) {
     return -1;
   }
diff --git a/avr910.c b/avr910.c
index bcc71d60..3e14fdc1 100644
--- a/avr910.c
+++ b/avr910.c
@@ -370,7 +370,8 @@ static int avr910_open(PROGRAMMER * pgm, char * port)
   }
 
   strcpy(pgm->port, port);
-  pinfo.baud = pgm->baudrate;
+  pinfo.serialinfo.baud = pgm->baudrate;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
   if (serial_open(port, pinfo, &pgm->fd)==-1) {
     return -1;
   }
diff --git a/buspirate.c b/buspirate.c
index 90b50845..f2a6d9ec 100644
--- a/buspirate.c
+++ b/buspirate.c
@@ -427,7 +427,8 @@ static int buspirate_open(struct programmer_t *pgm, char * port)
 	if(pgm->baudrate == 0)
 		pgm->baudrate = 115200;
 
-	pinfo.baud = pgm->baudrate;
+	pinfo.serialinfo.baud = pgm->baudrate;
+	pinfo.serialinfo.cflags = SERIAL_8N1;
 	strcpy(pgm->port, port);
 	if (serial_open(port, pinfo, &pgm->fd)==-1) {
 		return -1;
diff --git a/butterfly.c b/butterfly.c
index de9a3175..1d5fafdf 100644
--- a/butterfly.c
+++ b/butterfly.c
@@ -391,7 +391,8 @@ static int butterfly_open(PROGRAMMER * pgm, char * port)
   if(pgm->baudrate == 0) {
     pgm->baudrate = 19200;
   }
-  pinfo.baud = pgm->baudrate;
+  pinfo.serialinfo.baud = pgm->baudrate;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
   if (serial_open(port, pinfo, &pgm->fd)==-1) {
     return -1;
   }
diff --git a/jtagmkI.c b/jtagmkI.c
index 2a5f27e4..fc5f3f70 100644
--- a/jtagmkI.c
+++ b/jtagmkI.c
@@ -553,7 +553,7 @@ static int jtagmkI_initialize(PROGRAMMER * pgm, AVRPART * p)
                 progname, pgm->baudrate);
       if (jtagmkI_setparm(pgm, PARM_BITRATE, b) == 0) {
         PDATA(pgm)->initial_baudrate = pgm->baudrate; /* don't adjust again later */
-        serial_setspeed(&pgm->fd, pgm->baudrate);
+        serial_setparams(&pgm->fd, pgm->baudrate, SERIAL_8N1);
       }
     }
   }
@@ -648,9 +648,10 @@ static int jtagmkI_open(PROGRAMMER * pgm, char * port)
 
   for (i = 0; i < sizeof(baudtab) / sizeof(baudtab[0]); i++) {
     union pinfo pinfo;
-    pinfo.baud = baudtab[i].baud;
+    pinfo.serialinfo.baud = baudtab[i].baud;
+    pinfo.serialinfo.cflags = SERIAL_8N1;
     avrdude_message(MSG_NOTICE2, "%s: jtagmkI_open(): trying to sync at baud rate %ld:\n",
-                      progname, pinfo.baud);
+                      progname, pinfo.serialinfo.baud);
     if (serial_open(port, pinfo, &pgm->fd)==-1) {
       return -1;
     }
@@ -697,7 +698,7 @@ static void jtagmkI_close(PROGRAMMER * pgm)
                 "trying to set baudrate to %d\n",
                 progname, PDATA(pgm)->initial_baudrate);
       if (jtagmkI_setparm(pgm, PARM_BITRATE, b) == 0) {
-        serial_setspeed(&pgm->fd, pgm->baudrate);
+        serial_setparams(&pgm->fd, pgm->baudrate, SERIAL_8N1);
       }
     }
   }
diff --git a/jtagmkII.c b/jtagmkII.c
index df10f9f9..be6a4ed6 100644
--- a/jtagmkII.c
+++ b/jtagmkII.c
@@ -1312,7 +1312,7 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p)
 		"trying to set baudrate to %d\n",
 		progname, pgm->baudrate);
       if (jtagmkII_setparm(pgm, PAR_BAUD_RATE, &b) == 0)
-	serial_setspeed(&pgm->fd, pgm->baudrate);
+	serial_setparams(&pgm->fd, pgm->baudrate, SERIAL_8N1);
     }
   }
   if ((pgm->flag & PGM_FL_IS_JTAG) && pgm->bitclock != 0.0) {
@@ -1490,7 +1490,8 @@ static int jtagmkII_open(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -1542,7 +1543,8 @@ static int jtagmkII_open_dw(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -1594,7 +1596,8 @@ static int jtagmkII_open_pdi(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -1647,7 +1650,8 @@ static int jtagmkII_dragon_open(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -1700,7 +1704,8 @@ static int jtagmkII_dragon_open_dw(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -1753,7 +1758,8 @@ static int jtagmkII_dragon_open_pdi(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -3330,7 +3336,8 @@ static int jtagmkII_open32(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
diff --git a/libavrdude.h b/libavrdude.h
index 2431a218..0d1bf3a3 100644
--- a/libavrdude.h
+++ b/libavrdude.h
@@ -530,9 +530,34 @@ union filedescriptor
   } usb;
 };
 
+#define SERIAL_CS5          0x0000
+#define SERIAL_CS6          0x0001
+#define SERIAL_CS7          0x0002
+#define SERIAL_CS8          0x0004
+
+#define SERIAL_NO_CSTOPB    0x0000
+#define SERIAL_CSTOPB       0x0008
+
+#define SERIAL_NO_CREAD     0x0000
+#define SERIAL_CREAD        0x0010
+
+#define SERIAL_NO_PARITY    0x0000
+#define SERIAL_PARENB       0x0020
+#define SERIAL_PARODD       0x0040
+
+#define SERIAL_NO_CLOCAL    0x0000
+#define SERIAL_CLOCAL       0x0080
+
+#define SERIAL_8N1 (SERIAL_CS8 | SERIAL_NO_CSTOPB | SERIAL_CREAD | SERIAL_NO_PARITY | SERIAL_CLOCAL)
+#define SERIAL_8E1 (SERIAL_CS8 | SERIAL_NO_CSTOPB | SERIAL_CREAD | SERIAL_PARENB    | SERIAL_CLOCAL)
+#define SERIAL_8E2 (SERIAL_CS8 | SERIAL_CSTOPB    | SERIAL_CREAD | SERIAL_PARENB    | SERIAL_CLOCAL)
+
 union pinfo
 {
-  long baud;
+  struct {
+    long baud;
+    unsigned long cflags;
+  } serialinfo;
   struct
   {
     unsigned short vid;
@@ -548,7 +573,7 @@ struct serial_device
 {
   // open should return -1 on error, other values on success
   int (*open)(char * port, union pinfo pinfo, union filedescriptor *fd); 
-  int (*setspeed)(union filedescriptor *fd, long baud);
+  int (*setparams)(union filedescriptor *fd, long baud, unsigned long cflags);
   void (*close)(union filedescriptor *fd);
 
   int (*send)(union filedescriptor *fd, const unsigned char * buf, size_t buflen);
@@ -570,7 +595,7 @@ extern struct serial_device avrdoper_serdev;
 extern struct serial_device usbhid_serdev;
 
 #define serial_open (serdev->open)
-#define serial_setspeed (serdev->setspeed)
+#define serial_setparams (serdev->setparams)
 #define serial_close (serdev->close)
 #define serial_send (serdev->send)
 #define serial_recv (serdev->recv)
diff --git a/ser_posix.c b/ser_posix.c
index 2f40d0e4..3d8e15a1 100644
--- a/ser_posix.c
+++ b/ser_posix.c
@@ -55,6 +55,8 @@ struct baud_mapping {
 /* There are a lot more baud rates we could handle, but what's the point? */
 
 static struct baud_mapping baud_lookup_table [] = {
+  { 300,    B300 },
+  { 600,    B600 },
   { 1200,   B1200 },
   { 2400,   B2400 },
   { 4800,   B4800 },
@@ -96,7 +98,20 @@ static speed_t serial_baud_lookup(long baud)
   return baud;
 }
 
-static int ser_setspeed(union filedescriptor *fd, long baud)
+static tcflag_t translate_flags(unsigned long cflags)
+{
+  return ((cflags & SERIAL_CS5)                      ? CS5    : 0) |
+         ((cflags & SERIAL_CS6)                      ? CS6    : 0) |
+         ((cflags & SERIAL_CS7)                      ? CS7    : 0) |
+         ((cflags & SERIAL_CS8)                      ? CS8    : 0) |
+         ((cflags & SERIAL_CSTOPB)                   ? CSTOPB : 0) |
+         ((cflags & SERIAL_CREAD)                    ? CREAD  : 0) |
+         ((cflags & (SERIAL_PARENB | SERIAL_PARODD)) ? PARENB : 0) |
+         ((cflags & SERIAL_PARODD)                   ? PARODD : 0) |
+         ((cflags & SERIAL_CLOCAL)                   ? CLOCAL : 0) ;
+}
+
+static int ser_setparams(union filedescriptor *fd, long baud, unsigned long cflags)
 {
   int rc;
   struct termios termios;
@@ -110,7 +125,7 @@ static int ser_setspeed(union filedescriptor *fd, long baud)
    */
   rc = tcgetattr(fd->ifd, &termios);
   if (rc < 0) {
-    avrdude_message(MSG_INFO, "%s: ser_setspeed(): tcgetattr() failed",
+    avrdude_message(MSG_INFO, "%s: ser_setparams(): tcgetattr() failed",
             progname);
     return -errno;
   }
@@ -125,7 +140,7 @@ static int ser_setspeed(union filedescriptor *fd, long baud)
   termios.c_iflag = IGNBRK;
   termios.c_oflag = 0;
   termios.c_lflag = 0;
-  termios.c_cflag = (CS8 | CREAD | CLOCAL);
+  termios.c_cflag = translate_flags(cflags);
   termios.c_cc[VMIN]  = 1;
   termios.c_cc[VTIME] = 0;
 
@@ -134,7 +149,7 @@ static int ser_setspeed(union filedescriptor *fd, long baud)
 
   rc = tcsetattr(fd->ifd, TCSANOW, &termios);
   if (rc < 0) {
-    avrdude_message(MSG_INFO, "%s: ser_setspeed(): tcsetattr() failed\n",
+    avrdude_message(MSG_INFO, "%s: ser_setparams(): tcsetattr() failed\n",
             progname);
     return -errno;
   }
@@ -298,7 +313,7 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp)
   /*
    * set serial line attributes
    */
-  rc = ser_setspeed(fdp, pinfo.baud);
+  rc = ser_setparams(fdp, pinfo.serialinfo.baud, pinfo.serialinfo.cflags);
   if (rc) {
     avrdude_message(MSG_INFO, "%s: ser_open(): can't set attributes for device \"%s\": %s\n",
                     progname, port, strerror(-rc));
@@ -308,7 +323,6 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp)
   return 0;
 }
 
-
 static void ser_close(union filedescriptor *fd)
 {
   /*
@@ -501,7 +515,7 @@ static int ser_drain(union filedescriptor *fd, int display)
 struct serial_device serial_serdev =
 {
   .open = ser_open,
-  .setspeed = ser_setspeed,
+  .setparams = ser_setparams,
   .close = ser_close,
   .send = ser_send,
   .recv = ser_recv,
diff --git a/ser_win32.c b/ser_win32.c
index 5fc17b1b..25412cf0 100644
--- a/ser_win32.c
+++ b/ser_win32.c
@@ -54,6 +54,8 @@ static unsigned char serial_over_ethernet = 0;
 /* HANDLE hComPort=INVALID_HANDLE_VALUE; */
 
 static struct baud_mapping baud_lookup_table [] = {
+  { 300,    CBR_300 },
+  { 600,    CBR_600 },
   { 1200,   CBR_1200 },
   { 2400,   CBR_2400 },
   { 4800,   CBR_4800 },
@@ -97,7 +99,7 @@ static BOOL serial_w32SetTimeOut(HANDLE hComPort, DWORD timeout) // in ms
 	return SetCommTimeouts(hComPort, &ctmo);
 }
 
-static int ser_setspeed(union filedescriptor *fd, long baud)
+static int ser_setparams(union filedescriptor *fd, long baud, unsigned long cflags)
 {
 	if (serial_over_ethernet) {
 		return -ENOTTY;
@@ -111,9 +113,39 @@ static int ser_setspeed(union filedescriptor *fd, long baud)
 		dcb.fBinary = 1;
 		dcb.fDtrControl = DTR_CONTROL_DISABLE;
 		dcb.fRtsControl = RTS_CONTROL_DISABLE;
-		dcb.ByteSize = 8;
-		dcb.Parity = NOPARITY;
-		dcb.StopBits = ONESTOPBIT;
+		switch ((cflags & (SERIAL_CS5 | SERIAL_CS6 | SERIAL_CS7 | SERIAL_CS8))) {
+			case SERIAL_CS5:
+				dcb.ByteSize = 5;
+				break;
+			case SERIAL_CS6:
+				dcb.ByteSize = 6;
+				break;
+			case SERIAL_CS7:
+				dcb.ByteSize = 7;
+				break;
+			case SERIAL_CS8:
+				dcb.ByteSize = 8;
+				break;
+		}
+		switch ((cflags & (SERIAL_NO_PARITY | SERIAL_PARENB | SERIAL_PARODD))) {
+			case SERIAL_NO_PARITY:
+				dcb.Parity = NOPARITY;
+				break;
+			case SERIAL_PARENB:
+				dcb.Parity = EVENPARITY;
+				break;
+			case SERIAL_PARODD:
+				dcb.Parity = ODDPARITY;
+				break;
+		}
+		switch ((cflags & (SERIAL_NO_CSTOPB | SERIAL_CSTOPB))) {
+			case SERIAL_NO_CSTOPB:
+				dcb.StopBits = ONESTOPBIT;
+				break;
+			case SERIAL_CSTOPB:
+				dcb.StopBits = TWOSTOPBITS;
+				break;
+		}
 
 		if (!SetCommState(hComPort, &dcb))
 			return -1;
@@ -283,7 +315,7 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp)
 	}
 
         fdp->pfd = (void *)hComPort;
-	if (ser_setspeed(fdp, pinfo.baud) != 0)
+	if (ser_setparams(fdp, pinfo.serialinfo.baud, pinfo.serialinfo.cflags) != 0)
 	{
 		CloseHandle(hComPort);
 		avrdude_message(MSG_INFO, "%s: ser_open(): can't set com-state for \"%s\"\n",
@@ -770,7 +802,7 @@ static int ser_drain(union filedescriptor *fd, int display)
 struct serial_device serial_serdev =
 {
   .open = ser_open,
-  .setspeed = ser_setspeed,
+  .setparams = ser_setparams,
   .close = ser_close,
   .send = ser_send,
   .recv = ser_recv,
diff --git a/stk500.c b/stk500.c
index ebb22a27..88944afc 100644
--- a/stk500.c
+++ b/stk500.c
@@ -657,7 +657,8 @@ static int stk500_open(PROGRAMMER * pgm, char * port)
 {
   union pinfo pinfo;
   strcpy(pgm->port, port);
-  pinfo.baud = pgm->baudrate? pgm->baudrate: 115200;
+  pinfo.serialinfo.baud = pgm->baudrate? pgm->baudrate: 115200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
   if (serial_open(port, pinfo, &pgm->fd)==-1) {
     return -1;
   }
diff --git a/stk500v2.c b/stk500v2.c
index 4687ad6b..f6c5b1ca 100644
--- a/stk500v2.c
+++ b/stk500v2.c
@@ -1603,12 +1603,12 @@ static void stk500v2_enable(PROGRAMMER * pgm)
 
 static int stk500v2_open(PROGRAMMER * pgm, char * port)
 {
-  union pinfo pinfo = { .baud = 115200 };
+  union pinfo pinfo = { .serialinfo.baud = 115200, .serialinfo.cflags = SERIAL_8N1 };
 
   DEBUG("STK500V2: stk500v2_open()\n");
 
   if (pgm->baudrate)
-    pinfo.baud = pgm->baudrate;
+    pinfo.serialinfo.baud = pgm->baudrate;
 
   PDATA(pgm)->pgmtype = PGMTYPE_UNKNOWN;
 
@@ -1671,12 +1671,12 @@ static int stk500v2_open(PROGRAMMER * pgm, char * port)
 
 static int stk600_open(PROGRAMMER * pgm, char * port)
 {
-  union pinfo pinfo = { .baud = 115200 };
+  union pinfo pinfo = { .serialinfo.baud = 115200, .serialinfo.cflags = SERIAL_8N1 };
 
   DEBUG("STK500V2: stk600_open()\n");
 
   if (pgm->baudrate)
-    pinfo.baud = pgm->baudrate;
+    pinfo.serialinfo.baud = pgm->baudrate;
 
   PDATA(pgm)->pgmtype = PGMTYPE_UNKNOWN;
 
@@ -3392,7 +3392,8 @@ static int stk500v2_jtagmkII_open(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -3503,7 +3504,8 @@ static int stk500v2_dragon_isp_open(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
@@ -3581,7 +3583,8 @@ static int stk500v2_dragon_hv_open(PROGRAMMER * pgm, char * port)
    * a higher baud rate, we switch to it later on, after establishing
    * the connection with the ICE.
    */
-  pinfo.baud = 19200;
+  pinfo.serialinfo.baud = 19200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /*
    * If the port name starts with "usb", divert the serial routines
diff --git a/wiring.c b/wiring.c
index 1dc4d6a6..c0a68055 100644
--- a/wiring.c
+++ b/wiring.c
@@ -150,7 +150,8 @@ static int wiring_open(PROGRAMMER * pgm, char * port)
   union pinfo pinfo;
 
   strcpy(pgm->port, port);
-  pinfo.baud = pgm->baudrate ? pgm->baudrate: 115200;
+  pinfo.serialinfo.baud = pgm->baudrate ? pgm->baudrate: 115200;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
   serial_open(port, pinfo, &pgm->fd);
 
   /* If we have a snoozetime, then we wait and do NOT toggle DTR/RTS */
diff --git a/xbee.c b/xbee.c
index 454dc6de..1f974e55 100644
--- a/xbee.c
+++ b/xbee.c
@@ -1198,7 +1198,7 @@ static int xbeedev_open(char *port, union pinfo pinfo,
                   (unsigned int)xbs->xbee_address[6],
                   (unsigned int)xbs->xbee_address[7]);
 
-  if (pinfo.baud) {
+  if (pinfo.serialinfo.baud) {
     /*
      * User supplied the correct baud rate.
      */
@@ -1222,7 +1222,7 @@ static int xbeedev_open(char *port, union pinfo pinfo,
      * plugged in.  The doubled clock rate means a doubled serial
      * rate.  Double 9600 baud == 19200 baud.
      */
-    pinfo.baud = 19200;
+    pinfo.serialinfo.baud = 19200;
   } else {
     /*
      * In normal mode, default to 9600.
@@ -1234,10 +1234,11 @@ static int xbeedev_open(char *port, union pinfo pinfo,
      * XBee baud rate we should select.  The baud rate of the AVR
      * device is irrelevant.
      */
-    pinfo.baud = 9600;
+    pinfo.serialinfo.baud = 9600;
   }
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
-  avrdude_message(MSG_NOTICE, "%s: Baud %ld\n", progname, (long)pinfo.baud);
+  avrdude_message(MSG_NOTICE, "%s: Baud %ld\n", progname, (long)pinfo.serialinfo.baud);
 
   {
     const int rc = xbs->serialDevice->open(tty, pinfo,
@@ -1640,7 +1641,8 @@ static int xbee_open(PROGRAMMER *pgm, char *port)
 {
   union pinfo pinfo;
   strcpy(pgm->port, port);
-  pinfo.baud = pgm->baudrate;
+  pinfo.serialinfo.baud = pgm->baudrate;
+  pinfo.serialinfo.cflags = SERIAL_8N1;
 
   /* Wireless is lossier than normal serial */
   serial_recv_timeout = 1000;

From 748bee8ecf6bec2640415903dcc4643180063f4f Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Wed, 8 Dec 2021 12:36:58 +0000
Subject: [PATCH 02/12] Basic read operations implemented

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1513 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 Makefile.am              |   11 +
 avrdude.conf.in          |    7 +
 avrdude/serialupdi.c     |  253 ++++++++
 avrdude/serialupdi.h     |   43 ++
 avrdude/updi_constants.h |  156 +++++
 avrdude/updi_link.c      |  825 ++++++++++++++++++++++++
 avrdude/updi_link.h      |   58 ++
 avrdude/updi_nvm.c       | 1277 ++++++++++++++++++++++++++++++++++++++
 avrdude/updi_nvm.h       |   51 ++
 avrdude/updi_readwrite.c |  320 ++++++++++
 avrdude/updi_readwrite.h |   51 ++
 avrdude/updi_state.c     |   55 ++
 avrdude/updi_state.h     |   85 +++
 pgm_type.c               |    2 +
 14 files changed, 3194 insertions(+)
 create mode 100644 avrdude/serialupdi.c
 create mode 100644 avrdude/serialupdi.h
 create mode 100644 avrdude/updi_constants.h
 create mode 100644 avrdude/updi_link.c
 create mode 100644 avrdude/updi_link.h
 create mode 100644 avrdude/updi_nvm.c
 create mode 100644 avrdude/updi_nvm.h
 create mode 100644 avrdude/updi_readwrite.c
 create mode 100644 avrdude/updi_readwrite.h
 create mode 100644 avrdude/updi_state.c
 create mode 100644 avrdude/updi_state.h

diff --git a/Makefile.am b/Makefile.am
index 2b812d17..ffe7ca90 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -183,6 +183,17 @@ libavrdude_a_SOURCES = \
 	tpi.h \
 	usbasp.c \
 	usbasp.h \
+	serialupdi.c \
+	serialupdi.h \
+	updi_constants.h \
+	updi_link.c \
+	updi_link.h \
+	updi_state.c \
+	updi_state.h \
+	updi_readwrite.c \
+	updi_readwrite.h \
+	updi_nvm.c \
+	updi_nvm.h \
 	usbdevs.h \
 	usb_hidapi.c \
 	usb_libusb.c \
diff --git a/avrdude.conf.in b/avrdude.conf.in
index 5d1a7013..837c9f54 100644
--- a/avrdude.conf.in
+++ b/avrdude.conf.in
@@ -613,6 +613,13 @@ programmer
   reset  = 3; # TMS 7
 ;
 
+programmer
+  id    = "serialupdi";
+  desc  = "SerialUPDI";
+  type  = "serialupdi";
+  connection_type = serial;
+;
+
 programmer
   id    = "avrisp";
   desc  = "Atmel AVR ISP";
diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
new file mode 100644
index 00000000..0f4e70c1
--- /dev/null
+++ b/avrdude/serialupdi.c
@@ -0,0 +1,253 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Interface to the SerialUPDI programmer.
+ *
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "serialupdi.h"
+#include "updi_link.h"
+#include "updi_state.h"
+#include "updi_readwrite.h"
+
+static void serialupdi_setup(PROGRAMMER * pgm)
+{
+  if ((pgm->cookie = malloc(sizeof(updi_state))) == 0) {
+    avrdude_message(MSG_INFO,
+	    "%s: serialupdi_setup(): Out of memory allocating private data\n",
+	    progname);
+    exit(1);
+  }
+  memset(pgm->cookie, 0, sizeof(updi_state));
+  updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
+}
+
+static void serialupdi_teardown(PROGRAMMER * pgm)
+{
+  free(pgm->cookie);
+}
+
+static int serialupdi_open(PROGRAMMER * pgm, char * port)
+{
+  strcpy(pgm->port, port);
+  return updi_link_open(pgm);
+}
+
+static int serialupdi_decode_sib(updi_sib_info * sib_info)
+{
+  char * str_ptr;
+
+  sib_info->sib_string[SIB_INFO_STRING_LENGTH]=0;
+  avrdude_message(MSG_DEBUG, "%s: Received SIB: [%s]\n", progname, sib_info->sib_string);
+  memset(sib_info->family_string, 0, SIB_INFO_FAMILY_LENGTH+1);
+  memset(sib_info->nvm_string, 0, SIB_INFO_NVM_LENGTH+1);
+  memset(sib_info->debug_string, 0, SIB_INFO_DEBUG_LENGTH+1);
+  memset(sib_info->pdi_string, 0, SIB_INFO_PDI_LENGTH+1);
+  memset(sib_info->pdi_string, 0, SIB_INFO_PDI_LENGTH+1);
+  memset(sib_info->extra_string, 0, SIB_INFO_EXTRA_LENGTH+1);
+
+  memcpy(sib_info->family_string, sib_info->sib_string, SIB_INFO_FAMILY_LENGTH);
+  memcpy(sib_info->nvm_string, sib_info->sib_string + 8, SIB_INFO_NVM_LENGTH);
+  memcpy(sib_info->debug_string, sib_info->sib_string + 11, SIB_INFO_DEBUG_LENGTH);
+  memcpy(sib_info->pdi_string, sib_info->sib_string + 15, SIB_INFO_PDI_LENGTH);
+  strcpy(sib_info->extra_string, (char *)sib_info->sib_string + 19);
+
+  str_ptr = strstr(sib_info->nvm_string, ":");
+  if (!str_ptr) {
+    avrdude_message(MSG_INFO, "%s: Incorrect format of NVM string\n", progname);
+    return -1;
+  }
+  sib_info->nvm_version = *(str_ptr+1);
+
+  str_ptr = strstr(sib_info->debug_string, ":");
+  if (!str_ptr) {
+    avrdude_message(MSG_INFO, "%s: Incorrect format of DEBUG string\n", progname);
+    return -1;
+  }
+  sib_info->debug_version = *(str_ptr+1);
+
+  avrdude_message(MSG_DEBUG, "%s: Device family ID: %s\n", progname, sib_info->family_string);
+  avrdude_message(MSG_DEBUG, "%s: NVM interface: %s\n", progname, sib_info->nvm_string);
+  avrdude_message(MSG_DEBUG, "%s: Debug interface: %s\n", progname, sib_info->debug_string);
+  avrdude_message(MSG_DEBUG, "%s: PDI oscillator: %s\n", progname, sib_info->pdi_string);
+  avrdude_message(MSG_DEBUG, "%s: Extra information: %s\n", progname, sib_info->extra_string);
+  switch (sib_info->nvm_version) {
+    case '0':
+      avrdude_message(MSG_INFO, "%s: NVM type 0: 16-bit, page oriented write\n", progname);
+      break;
+    case '2':
+      avrdude_message(MSG_INFO, "%s: NVM type 2: 24-bit, word oriented write\n", progname);
+      break;
+    case '3':
+      avrdude_message(MSG_INFO, "%s: NVM type 3: 16-bit, page oriented\n", progname);
+      break;
+    default:
+      avrdude_message(MSG_INFO, "%s: Unsupported NVM type: %c, please update software\n", progname, sib_info->nvm_version);
+      return -1;
+  }
+  return 0;
+}
+
+static void serialupdi_close(PROGRAMMER * pgm)
+{
+  updi_link_close(pgm);
+}
+
+static int serialupdi_initialize(PROGRAMMER * pgm, AVRPART * p)
+{
+  updi_sib_info * sib_info = updi_get_sib_info(pgm);
+
+  if (updi_link_init(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_INFO, "%s: UPDI link initialization OK\n", progname);
+  if (updi_read_sib(pgm, sib_info->sib_string, 32) < 0) {
+    avrdude_message(MSG_INFO, "%s: Read SIB operation failed\n", progname);
+    return -1;
+  }
+  if (serialupdi_decode_sib(sib_info) < 0) {
+    avrdude_message(MSG_INFO, "%s: Decode SIB_INFO failed\n", progname);
+    return -1;
+  }
+  updi_set_nvm_mode(pgm, sib_info->nvm_version);
+  if (sib_info->nvm_version == '2') {
+    updi_set_datalink_mode(pgm, UPDI_LINK_MODE_24BIT);
+  } else {
+    updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
+  }
+  
+  return 0;
+}
+
+static void serialupdi_disable(PROGRAMMER * pgm)
+{
+  /* Do nothing. */
+
+  return;
+}
+
+static void serialupdi_enable(PROGRAMMER * pgm)
+{
+  /* Do nothing. */
+
+  return;
+}
+
+static void serialupdi_display(PROGRAMMER * pgm, const char * p)
+{
+  return;
+}
+
+static int serialupdi_cmd(PROGRAMMER * pgm, const unsigned char * cmd,
+                          unsigned char * res)
+{
+  avrdude_message(MSG_INFO, "%s: error: cmd %s[%s] not implemented yet\n",
+    	    progname, cmd, res);
+  return -1;
+}
+
+static int serialupdi_program_enable(PROGRAMMER * pgm, AVRPART * p)
+{
+  avrdude_message(MSG_INFO, "%s: error: program enable not implemented yet\n",
+    	    progname);
+  return -1;
+}
+
+static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
+{
+  avrdude_message(MSG_INFO, "%s: error: chip erase not implemented yet\n",
+    	    progname);
+  return -1;
+}
+
+static int serialupdi_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, 
+                                unsigned long addr, unsigned char * value)
+{
+//  avrdude_message(MSG_INFO, "%s: error: read byte not implemented yet\n",
+//    	            progname);
+  return updi_read_byte(pgm, mem->offset + addr, value);
+//  return -1;
+}
+
+static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                 unsigned int page_size,
+                                 unsigned int addr, unsigned int n_bytes)
+{
+  avrdude_message(MSG_INFO, "%s: error: paged load not implemented yet\n",
+    	    progname);
+  return -1;
+}
+
+static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                  unsigned int page_size,
+                                  unsigned int addr, unsigned int n_bytes)
+{
+  avrdude_message(MSG_INFO, "%s: error: paged write not implemented yet\n",
+    	    progname);
+  return -1;
+}
+
+void serialupdi_initpgm(PROGRAMMER * pgm)
+{
+  strcpy(pgm->type, "serialupdi");
+
+  /*
+   * mandatory functions
+   */
+
+  pgm->initialize     = serialupdi_initialize;
+  pgm->display        = serialupdi_display;
+  pgm->enable         = serialupdi_enable;
+  pgm->disable        = serialupdi_disable;
+  pgm->program_enable = serialupdi_program_enable;
+  pgm->chip_erase     = serialupdi_chip_erase;
+  pgm->cmd            = serialupdi_cmd;
+  pgm->open           = serialupdi_open;
+  pgm->close          = serialupdi_close;
+  pgm->read_byte      = serialupdi_read_byte;
+  pgm->write_byte     = avr_write_byte_default;
+
+  /*
+   * optional functions
+   */
+
+  pgm->paged_write    = serialupdi_paged_write;
+  pgm->paged_load     = serialupdi_paged_load;
+  pgm->setup          = serialupdi_setup;
+  pgm->teardown       = serialupdi_teardown;
+
+}
+
+const char serialupdi_desc[] = "Driver for SerialUPDI programmers";
diff --git a/avrdude/serialupdi.h b/avrdude/serialupdi.h
new file mode 100644
index 00000000..ff7270d5
--- /dev/null
+++ b/avrdude/serialupdi.h
@@ -0,0 +1,43 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef serialupdi_h
+#define serialupdi_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char serialupdi_desc[];
+void serialupdi_initpgm (PROGRAMMER * pgm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* serialupdi_h */
diff --git a/avrdude/updi_constants.h b/avrdude/updi_constants.h
new file mode 100644
index 00000000..ff8a446f
--- /dev/null
+++ b/avrdude/updi_constants.h
@@ -0,0 +1,156 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_constants_h
+#define updi_constants_h
+
+#define UPDI_BREAK       0x00
+
+#define UPDI_LDS         0x00
+#define UPDI_STS         0x40
+#define UPDI_LD          0x20
+#define UPDI_ST          0x60
+#define UPDI_LDCS        0x80
+#define UPDI_STCS        0xC0
+#define UPDI_REPEAT      0xA0
+#define UPDI_KEY         0xE0
+
+#define UPDI_PTR         0x00
+#define UPDI_PTR_INC     0x04
+#define UPDI_PTR_ADDRESS 0x08
+
+#define UPDI_ADDRESS_8   0x00
+#define UPDI_ADDRESS_16  0x04
+#define UPDI_ADDRESS_24  0x08
+
+#define UPDI_DATA_8      0x00
+#define UPDI_DATA_16     0x01
+#define UPDI_DATA_24     0x02
+
+#define UPDI_KEY_SIB     0x04
+#define UPDI_KEY_KEY     0x00
+
+#define UPDI_KEY_64      0x00
+#define UPDI_KEY_128     0x01
+#define UPDI_KEY_256     0x02
+
+#define UPDI_SIB_8BYTES  UPDI_KEY_64
+#define UPDI_SIB_16BYTES UPDI_KEY_128
+#define UPDI_SIB_32BYTES UPDI_KEY_256
+
+#define UPDI_REPEAT_BYTE 0x00
+#define UPDI_REPEAT_WORD 0x01
+
+#define UPDI_PHY_SYNC    0x55
+#define UPDI_PHY_ACK     0x40
+
+#define UPDI_MAX_REPEAT_SIZE (0xFF+1) // Repeat counter of 1-byte, with off-by-one counting
+
+//# CS and ASI Register Address map
+#define UPDI_CS_STATUSA     0x00
+#define UPDI_CS_STATUSB     0x01
+#define UPDI_CS_CTRLA       0x02
+#define UPDI_CS_CTRLB       0x03
+#define UPDI_ASI_KEY_STATUS 0x07
+#define UPDI_ASI_RESET_REQ  0x08
+#define UPDI_ASI_CTRLA      0x09
+#define UPDI_ASI_SYS_CTRLA  0x0A
+#define UPDI_ASI_SYS_STATUS 0x0B
+#define UPDI_ASI_CRC_STATUS 0x0C
+
+#define UPDI_CTRLA_IBDLY_BIT    7
+#define UPDI_CTRLB_CCDETDIS_BIT 3
+#define UPDI_CTRLB_UPDIDIS_BIT  2
+
+#define UPDI_KEY_NVM       "NVMProg "
+#define UPDI_KEY_CHIPERASE "NVMErase"
+#define UPDI_KEY_UROW      "NVMUs&te"
+
+#define UPDI_ASI_STATUSA_REVID 4
+#define UPDI_ASI_STATUSB_PESIG 0
+
+#define UPDI_ASI_KEY_STATUS_CHIPERASE  3
+#define UPDI_ASI_KEY_STATUS_NVMPROG    4
+#define UPDI_ASI_KEY_STATUS_UROWWRITE  5
+
+#define UPDI_ASI_SYS_STATUS_RSTSYS     5
+#define UPDI_ASI_SYS_STATUS_INSLEEP    4
+#define UPDI_ASI_SYS_STATUS_NVMPROG    3
+#define UPDI_ASI_SYS_STATUS_UROWPROG   2
+#define UPDI_ASI_SYS_STATUS_LOCKSTATUS 0
+
+#define UPDI_ASI_SYS_CTRLA_UROW_FINAL  1
+
+#define UPDI_RESET_REQ_VALUE  0x59
+
+// FLASH CONTROLLER
+#define UPDI_NVMCTRL_CTRLA    0x00
+#define UPDI_NVMCTRL_CTRLB    0x01
+#define UPDI_NVMCTRL_STATUS   0x02
+#define UPDI_NVMCTRL_INTCTRL  0x03
+#define UPDI_NVMCTRL_INTFLAGS 0x04
+#define UPDI_NVMCTRL_DATAL    0x06
+#define UPDI_NVMCTRL_DATAH    0x07
+#define UPDI_NVMCTRL_ADDRL    0x08
+#define UPDI_NVMCTRL_ADDRH    0x09
+
+// NVMCTRL v0 CTRLA
+#define UPDI_V0_NVMCTRL_CTRLA_NOP              0x00
+#define UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE       0x01
+#define UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE       0x02
+#define UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE 0x03
+#define UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR  0x04
+#define UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE       0x05
+#define UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM     0x06
+#define UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE       0x07
+
+// NVMCTRL v2 CTRLA
+#define UPDI_V2_NVMCTRL_CTRLA_NOCMD              0x00
+#define UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE        0x02
+#define UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE   0x08
+#define UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE 0x13
+#define UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE         0x20
+#define UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE       0x30
+
+// NVMCTRL v3 CTRLA
+#define UPDI_V3_NVMCTRL_CTRLA_NOCMD                    0x00
+#define UPDI_V3_NVMCTRL_CTRLA_NOP                      0x01
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE         0x04
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE_WRITE   0x05
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE         0x08
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR  0x0F
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_WRITE        0x14
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE  0x15
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE        0x17
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_BUFFER_CLEAR 0x1F
+#define UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE               0x20
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE             0x30
+
+#define UPDI_NVM_STATUS_WRITE_ERROR 2
+#define UPDI_NVM_STATUS_EEPROM_BUSY 1
+#define UPDI_NVM_STATUS_FLASH_BUSY  0
+
+#endif /* updi_constants_h */
diff --git a/avrdude/updi_link.c b/avrdude/updi_link.c
new file mode 100644
index 00000000..33c3da1d
--- /dev/null
+++ b/avrdude/updi_link.c
@@ -0,0 +1,825 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "updi_link.h"
+#include "updi_constants.h"
+#include "updi_state.h"
+
+#include <sys/time.h>
+
+void msleep(int tms)
+{
+    struct timeval tv;
+    tv.tv_sec  = tms / 1000;
+    tv.tv_usec = (tms % 1000) * 1000;
+    select (0, NULL, NULL, NULL, &tv);
+}
+
+static int updi_physical_open(PROGRAMMER* pgm, int baudrate, unsigned long cflags)
+{
+  serial_recv_timeout = 100;
+  union pinfo pinfo;
+
+  pinfo.serialinfo.baud = baudrate;
+  pinfo.serialinfo.cflags = cflags;
+
+  avrdude_message(MSG_DEBUG, "%s: Opening serial port...\n", progname);
+
+  if (serial_open(pgm->port, pinfo, &pgm->fd)==-1) {
+
+    avrdude_message(MSG_INFO, "%s: Serial port open failed!\n", progname);
+    return -1;
+  }
+
+  /*
+   * drain any extraneous input
+   */
+  serial_drain(&pgm->fd, 0);
+
+  return 0;
+}
+
+static void updi_physical_close(PROGRAMMER* pgm)
+{
+  serial_close(&pgm->fd);
+  pgm->fd.ifd = -1;
+}
+
+static int updi_physical_send(PROGRAMMER * pgm, unsigned char * buf, size_t len)
+{
+  size_t i;
+  int rv;
+
+  avrdude_message(MSG_DEBUG, "%s: Sending %lu bytes [", progname, len);
+  for (i=0; i<len; i++) {
+    avrdude_message(MSG_DEBUG, "0x%02x", buf[i]);
+    if (i<len-1) {
+      avrdude_message(MSG_DEBUG, ", ");
+    }
+  }
+  avrdude_message(MSG_DEBUG, "]\n");
+
+  rv = serial_send(&pgm->fd, buf, len);
+  serial_recv(&pgm->fd, buf, len);
+  return rv;
+}
+
+static int updi_physical_recv(PROGRAMMER * pgm, unsigned char * buf, size_t len)
+{
+  size_t i;
+  int rv;
+
+  rv = serial_recv(&pgm->fd, buf, len);
+  if (rv < 0) {
+    avrdude_message(MSG_DEBUG,
+      "%s: serialupdi_recv(): programmer is not responding\n",
+      progname);
+    return -1;
+  }
+
+  avrdude_message(MSG_DEBUG, "%s: Received %lu bytes [", progname, len);
+  for (i=0; i<len; i++) {
+    avrdude_message(MSG_DEBUG, "0x%02x", buf[i]);
+    if (i<len-1) {
+      avrdude_message(MSG_DEBUG, ", ");
+    }
+  }
+  avrdude_message(MSG_DEBUG, "]\n");
+
+  return len;
+}
+
+static int updi_physical_send_double_break(PROGRAMMER * pgm)
+{
+  unsigned char buffer[1];
+
+  avrdude_message(MSG_DEBUG, "%s: Sending double break\n", progname);
+
+  updi_physical_close(pgm);
+
+  if (updi_physical_open(pgm, 300, SERIAL_8E1)==-1) {
+
+    return -1;
+  }
+
+  buffer[0] = UPDI_BREAK;
+
+  serial_send(&pgm->fd, buffer, 1);
+  serial_recv(&pgm->fd, buffer, 1);
+
+  msleep(100);
+
+  buffer[0] = UPDI_BREAK;
+
+  serial_send(&pgm->fd, buffer, 1);
+  serial_recv(&pgm->fd, buffer, 1);
+
+  updi_physical_close(pgm);
+
+  return updi_physical_open(pgm, pgm->baudrate? pgm->baudrate: 115200, SERIAL_8E2);
+}
+
+int updi_physical_sib(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size)
+{
+/*
+    def sib(self):
+        """
+        System information block is just a string coming back from a SIB command
+        """
+        self.send([
+            constants.UPDI_PHY_SYNC,
+            constants.UPDI_KEY | constants.UPDI_KEY_SIB | constants.UPDI_SIB_32BYTES])
+        return self.ser.readline()
+*/
+  unsigned char send_buffer[2];
+
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_KEY | UPDI_KEY_SIB | UPDI_SIB_32BYTES;
+
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_INFO, "%s: SIB request send failed\n", progname);
+    return -1;
+  }
+
+  return updi_physical_recv(pgm, buffer, size);
+}
+
+int updi_link_open(PROGRAMMER * pgm) 
+{
+  return updi_physical_open(pgm, pgm->baudrate? pgm->baudrate: 115200, 
+                            (SERIAL_CS8 | SERIAL_CSTOPB | SERIAL_CREAD | SERIAL_PARENB | SERIAL_CLOCAL));
+}
+
+void updi_link_close(PROGRAMMER * pgm)
+{
+  updi_physical_close(pgm);
+}
+
+static int updi_link_init_session_parameters(PROGRAMMER * pgm) 
+{
+/*
+    def _init_session_parameters(self):
+        """
+        Set the inter-byte delay bit and disable collision detection
+        """
+        self.stcs(constants.UPDI_CS_CTRLB, 1 << constants.UPDI_CTRLB_CCDETDIS_BIT)
+        self.stcs(constants.UPDI_CS_CTRLA, 1 << constants.UPDI_CTRLA_IBDLY_BIT)
+*/
+  if (updi_link_stcs(pgm, UPDI_CS_CTRLB, 1 << UPDI_CTRLB_CCDETDIS_BIT) < 0) {
+    return -1;
+  }
+
+  if (updi_link_stcs(pgm, UPDI_CS_CTRLA, 1 << UPDI_CTRLA_IBDLY_BIT) < 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int updi_link_check(PROGRAMMER * pgm)
+{
+/*
+    def _check_datalink(self):
+        """
+        Check UPDI by loading CS STATUSA
+        """
+        try:
+            if self.ldcs(constants.UPDI_CS_STATUSA) != 0:
+                self.logger.info("UPDI init OK")
+                return True
+        except PymcuprogError:
+            self.logger.warning("Check failed")
+            return False
+        self.logger.info("UPDI not OK - reinitialisation required")
+        return False
+*/
+  int result;
+  uint8_t value;
+  result = updi_link_ldcs(pgm, UPDI_CS_STATUSA, &value);
+  if (result < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Check failed\n", progname);
+    return -1;
+  } else {
+    if (value > 0) {
+      avrdude_message(MSG_DEBUG, "%s: UDPI init OK\n", progname);
+      return 0;
+    } else {
+      avrdude_message(MSG_DEBUG, "%s: UDPI not OK - reinitialisation required\n", progname);
+      return -1;
+    }
+  }
+}
+
+
+int updi_link_init(PROGRAMMER * pgm)
+{
+/*
+    def init_datalink(self):
+        """
+        Init DL layer
+        """
+        self._init_session_parameters()
+        # Check
+        if not self._check_datalink():
+            # Send double break if all is not well, and re-check
+            self.updi_phy.send_double_break()
+            self._init_session_parameters()
+            if not self._check_datalink():
+                raise PymcuprogError("UPDI initialisation failed")
+*/
+  if (updi_link_init_session_parameters(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: Session initialisation failed\n", progname);
+    return -1;
+  }
+
+  if (updi_link_check(pgm) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Datalink not active, resetting...\n", progname);
+    if (updi_physical_send_double_break(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: Datalink initialisation failed\n", progname);
+      return -1;
+    }
+    if (updi_link_init_session_parameters(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: Session initialisation failed\n", progname);
+      return -1;
+    }
+    if (updi_link_check(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: Restoring datalink failed\n", progname);
+      return -1;
+    }
+  }
+  return 0;
+}
+
+int updi_link_ldcs(PROGRAMMER * pgm, uint8_t address, uint8_t * value) 
+{
+/*
+    def ldcs(self, address):
+        """
+        Load data from Control/Status space
+
+        :param address: address to load
+        """
+        self.logger.debug("LDCS from 0x%02X", address)
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)])
+        response = self.updi_phy.receive(self.LDCS_RESPONSE_BYTES)
+        numbytes_received = len(response)
+        if numbytes_received != self.LDCS_RESPONSE_BYTES:
+            raise PymcuprogError("Unexpected number of bytes in response: "
+                                 "{} byte(s) expected {} byte(s)".format(numbytes_received, self.LDCS_RESPONSE_BYTES))
+
+        return response[0]
+*/
+  unsigned char buffer[2];
+  int result;
+  avrdude_message(MSG_DEBUG, "%s: LDCS from 0x%02X\n", progname, address);
+  buffer[0]=UPDI_PHY_SYNC;
+  buffer[1]=UPDI_LDCS | (address & 0x0F);
+  if (updi_physical_send(pgm, buffer, 2) < 0) {
+    avrdude_message(MSG_INFO, "%s: LDCS send operation failed\n", progname);
+    return -1;
+  }
+  result = updi_physical_recv(pgm, buffer, 1);
+  if (result != 1) {
+    if (result >= 0) {
+      avrdude_message(MSG_INFO, "%s: Incorrect response size, received %d instead of %d bytes\n", progname, result, 1);
+    }
+    return -1;
+  }
+  * value = buffer[0];
+  return 0;
+}
+
+int updi_link_stcs(PROGRAMMER * pgm, uint8_t address, uint8_t value)
+{
+/*
+    def stcs(self, address, value):
+        """
+        Store a value to Control/Status space
+
+        :param address: address to store to
+        :param value: value to write
+        """
+        self.logger.debug("STCS to 0x%02X", address)
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_STCS | (address & 0x0F), value])
+*/
+  unsigned char buffer[3];
+  avrdude_message(MSG_DEBUG, "%s: STCS 0x%02X to address 0x%02X\n", progname, value, address);
+  buffer[0] = UPDI_PHY_SYNC;
+  buffer[1] = UPDI_STCS | (address & 0x0F);
+  buffer[2] = value;
+  return updi_physical_send(pgm, buffer, 3);
+}
+
+int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def ld_ptr_inc(self, size):
+        """
+        Loads a number of bytes from the pointer location with pointer post-increment
+ 
+        :param size: number of bytes to load
+        :return: values read
+        """
+        self.logger.debug("LD8 from ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC |
+                            constants.UPDI_DATA_8])
+        return self.updi_phy.receive(size)
+*/
+  unsigned char send_buffer[2];
+  avrdude_message(MSG_DEBUG, "%s: LD8 from ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LD | UPDI_PTR_INC | UPDI_DATA_8;
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_INFO, "%s: LD_PTR_INC send operation failed\n", progname);
+    return -1;
+  }
+  return updi_physical_recv(pgm, buffer, size);
+}
+
+int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words)
+{
+/*
+    def ld_ptr_inc16(self, words):
+        """
+        Load a 16-bit word value from the pointer location with pointer post-increment
+
+        :param words: number of words to load
+        :return: values read
+        """
+        self.logger.debug("LD16 from ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC |
+                            constants.UPDI_DATA_16])
+        return self.updi_phy.receive(words << 1)
+*/
+  unsigned char send_buffer[2];
+  avrdude_message(MSG_DEBUG, "%s: LD16 from ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LD | UPDI_PTR_INC | UPDI_DATA_16;
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_INFO, "%s: LD_PTR_INC send operation failed\n", progname);
+    return -1;
+  }
+  return updi_physical_recv(pgm, buffer, words << 2);
+}
+
+int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def st_ptr_inc(self, data):
+        """
+        Store data to the pointer location with pointer post-increment
+
+        :param data: data to store
+        """
+        self.logger.debug("ST8 to *ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8,
+                            data[0]])
+        response = self.updi_phy.receive(1)
+
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("ACK error with st_ptr_inc")
+
+        num = 1
+        while num < len(data):
+            self.updi_phy.send([data[num]])
+            response = self.updi_phy.receive(1)
+
+            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+                raise PymcuprogError("Error with st_ptr_inc")
+            num += 1
+*/
+  unsigned char send_buffer[3];
+  unsigned char recv_buffer[1];
+  int response;
+  int num = 1;
+  avrdude_message(MSG_DEBUG, "%s: ST8 to *ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_8;
+  send_buffer[2] = buffer[0];
+  if (updi_physical_send(pgm, send_buffer, 3) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR_INC send operation failed\n", progname);
+    return -1;
+  }
+
+  response = updi_physical_recv(pgm, recv_buffer, 1);
+
+  if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_INFO, "%s: ACK was expected but not received\n", progname);
+    return -1;
+  }
+
+  while (num < size) {
+    send_buffer[0]=buffer[num];
+    if (updi_physical_send(pgm, send_buffer, 1) < 0) {
+      avrdude_message(MSG_INFO, "%s: ST_PTR_INC data send operation failed\n", progname);
+      return -1;
+    }
+    response = updi_physical_recv(pgm, recv_buffer, 1);
+
+    if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+      avrdude_message(MSG_INFO, "%s: Data ACK was expected but not received\n", progname);
+      return -1;
+    }
+    num++;
+  }
+
+  return 0;
+}
+
+int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words)
+{
+/*
+    def st_ptr_inc16(self, data):
+        """
+        Store a 16-bit word value to the pointer location with pointer post-increment
+
+        :param data: data to store
+        """
+        self.logger.debug("ST16 to *ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |
+                            constants.UPDI_DATA_16, data[0], data[1]])
+        response = self.updi_phy.receive(1)
+
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("ACK error with st_ptr_inc16")
+
+        num = 2
+        while num < len(data):
+            self.updi_phy.send([data[num], data[num + 1]])
+            response = self.updi_phy.receive(1)
+
+            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+                raise PymcuprogError("Error with st_ptr_inc16")
+            num += 2
+*/
+  unsigned char send_buffer[4];
+  unsigned char recv_buffer[1];
+  int response;
+  int num = 2;
+  avrdude_message(MSG_DEBUG, "%s: ST16 to *ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16;
+  send_buffer[2] = buffer[0];
+  send_buffer[3] = buffer[1];
+  if (updi_physical_send(pgm, send_buffer, 4) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR_INC16 send operation failed\n", progname);
+    return -1;
+  }
+
+  response = updi_physical_recv(pgm, recv_buffer, 1);
+
+  if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_INFO, "%s: ACK was expected but not received\n", progname);
+    return -1;
+  }
+
+  while (num < words) {
+    send_buffer[0]=buffer[num];
+    send_buffer[1]=buffer[num+1];
+    if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+      avrdude_message(MSG_INFO, "%s: ST_PTR_INC data send operation failed\n", progname);
+      return -1;
+    }
+    response = updi_physical_recv(pgm, recv_buffer, 1);
+
+    if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+      avrdude_message(MSG_INFO, "%s: Data ACK was expected but not received\n", progname);
+      return -1;
+    }
+    num+=2;
+  }
+
+  return 0;
+}
+
+int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats)
+{
+/*
+    def repeat(self, repeats):
+        """
+        Store a value to the repeat counter
+
+        :param repeats: number of repeats requested
+        """
+        self.logger.debug("Repeat %d", repeats)
+        if (repeats - 1) > constants.UPDI_MAX_REPEAT_SIZE:
+            self.logger.error("Invalid repeat count of %d", repeats)
+            raise Exception("Invalid repeat count!")
+        repeats -= 1
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE,
+                            repeats & 0xFF])
+*/
+  unsigned char buffer[3];
+  avrdude_message(MSG_DEBUG, "%s: Repeat %d\n", progname, repeats);
+  if ((repeats - 1) > UPDI_MAX_REPEAT_SIZE) {
+    avrdude_message(MSG_INFO, "%s: Invalid repeat count of %d\n", progname, repeats);
+    return -1;
+  }
+  repeats-=1;
+  buffer[0] = UPDI_PHY_SYNC;
+  buffer[1] = UPDI_REPEAT | UPDI_REPEAT_BYTE;
+  buffer[2] = repeats & 0xFF;
+  return updi_physical_send(pgm, buffer, 3);
+}
+
+int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def read_sib(self):
+        """
+        Read the SIB
+        """
+        return self.updi_phy.sib()
+*/
+  return updi_physical_sib(pgm, buffer, size);
+}
+
+int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size)
+{
+/*
+    def key(self, size, key):
+        """
+        Write a key
+ 
+        :param size: size of key (0=64B, 1=128B, 2=256B)
+        :param key: key value
+        """
+        self.logger.debug("Writing key")
+        if len(key) != 8 << size:
+            raise PymcuprogError("Invalid KEY length!")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_KEY | constants.UPDI_KEY_KEY | size])
+        self.updi_phy.send(list(reversed(list(key))))
+*/
+  unsigned char send_buffer[2];
+  unsigned char reversed_key[256];
+  int index;
+  avrdude_message(MSG_DEBUG, "%s: UPDI writing key\n", progname);
+  if (size != (8 << size_type)) {
+    avrdude_message(MSG_INFO, "%s: Invalid key length\n", progname);
+    return -1;
+  }
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_KEY | UPDI_KEY_KEY | size_type;
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI key send message failed\n", progname);
+    return -1;
+  }
+  /* reverse key contents */
+  for (index=0; index<size; index++) {
+    reversed_key[index] = buffer[size-index-1];
+  }
+  return updi_physical_send(pgm, reversed_key, size);
+}
+
+int updi_link_ld(PROGRAMMER * pgm, uint32_t address, uint8_t * value)
+{
+/*
+    def ld(self, address):
+        """
+        Load a single byte direct from a 24-bit address
+
+        :param address: address to load from
+        :return: value read
+        """
+        self.logger.info("LD from 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self.updi_phy.receive(1)[0]
+*/
+  unsigned char send_buffer[5];
+  unsigned char recv_buffer[1];
+  avrdude_message(MSG_DEBUG, "%s: LD from 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LDS | UPDI_DATA_8 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_INFO, "%s: LD operation send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: LD operation recv failed\n", progname);
+    return -1;
+  }
+  * value = recv_buffer[0];
+  return 0;
+}
+
+int updi_link_ld16(PROGRAMMER * pgm, uint32_t address, uint16_t * value)
+{
+/*
+    def ld16(self, address):
+        """
+        Load a 16-bit word directly from a 24-bit address
+
+        :param address: address to load from
+        :return: values read
+        """
+        self.logger.info("LD from 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self.updi_phy.receive(2)
+*/
+  unsigned char send_buffer[5];
+  unsigned char recv_buffer[2];
+  avrdude_message(MSG_DEBUG, "%s: LD16 from 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LDS | UPDI_DATA_16 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_INFO, "%s: LD16 operation send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 2) < 0) {
+    avrdude_message(MSG_INFO, "%s: LD16 operation recv failed\n", progname);
+    return -1;
+  }
+  * value = (recv_buffer[0] << 8 | recv_buffer[1]);
+  return 0;
+}
+
+static int updi_link_st_data_phase(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size)
+{
+/*
+    def _st_data_phase(self, values):
+        """
+        Performs data phase of transaction:
+        * receive ACK
+        * send data
+
+        :param values: bytearray of value(s) to send
+        """
+        response = self.updi_phy.receive(1)
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("Error with st")
+
+        self.updi_phy.send(values)
+        response = self.updi_phy.receive(1)
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("Error with st")
+*/
+  unsigned char recv_buffer[1];
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI data phase recv failed on first ACK\n", progname);
+    return -1;
+  }
+  if (recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_INFO, "%s: UPDI data phase expected first ACK\n", progname);
+    return -1;
+  }
+  if (updi_physical_send(pgm, buffer, size) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI data phase send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI data phase recv failed on second ACK\n", progname);
+    return -1;
+  }
+  if (recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_INFO, "%s: UPDI data phase expected second ACK\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+int updi_link_st(PROGRAMMER * pgm, uint32_t address, uint8_t value)
+{
+/*
+    def st(self, address, value):
+        """
+        Store a single byte value directly to a 24-bit address
+
+        :param address: address to write to
+        :param value: value to write
+        """
+        self.logger.info("ST to 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self._st_data_phase([value & 0xFF])
+*/
+  unsigned char send_buffer[5];
+  avrdude_message(MSG_DEBUG, "%s: ST to 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_STS | UPDI_DATA_8 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST operation send failed\n", progname);
+    return -1;
+  }
+  send_buffer[0] = value;
+  return updi_link_st_data_phase(pgm, send_buffer, 1);
+}
+
+int updi_link_st16(PROGRAMMER * pgm, uint32_t address, uint16_t value)
+{
+/*
+    def st16(self, address, value):
+        """
+        Store a 16-bit word value directly to a 24-bit address
+
+        :param address: address to write to
+        :param value: value to write
+        """
+        self.logger.info("ST to 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self._st_data_phase([value & 0xFF, (value >> 8) & 0xFF])
+*/
+  unsigned char send_buffer[5];
+  avrdude_message(MSG_DEBUG, "%s: ST16 to 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_STS | UPDI_DATA_16 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST16 operation send failed\n", progname);
+    return -1;
+  }
+  send_buffer[0] = value & 0xFF;
+  send_buffer[1] = (value >> 8) & 0xFF;
+  return updi_link_st_data_phase(pgm, send_buffer, 2);
+}
+
+int updi_link_st_ptr(PROGRAMMER * pgm, uint32_t address)
+{
+/*
+    def st_ptr(self, address):
+        """
+        Set the pointer location
+
+        :param address: address to write
+        """
+        self.logger.info("ST to ptr")
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_24,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        response = self.updi_phy.receive(1)
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("Error with st_ptr")
+*/
+  unsigned char send_buffer[5];
+  unsigned char recv_buffer[1];
+  avrdude_message(MSG_DEBUG, "%s: ST_PTR to 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_STS | UPDI_ST | UPDI_PTR_ADDRESS | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_DATA_24 : UPDI_DATA_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR operation send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI ST_PTR recv failed on ACK\n", progname);
+    return -1;
+  }
+  if (recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_INFO, "%s: UPDI ST_PTR expected ACK\n", progname);
+    return -1;
+  }
+  return 0;
+}
diff --git a/avrdude/updi_link.h b/avrdude/updi_link.h
new file mode 100644
index 00000000..e6848b0d
--- /dev/null
+++ b/avrdude/updi_link.h
@@ -0,0 +1,58 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_link_h
+#define updi_link_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int updi_link_open(PROGRAMMER * pgm);
+void updi_link_close(PROGRAMMER * pgm);
+int updi_link_init(PROGRAMMER * pgm);
+int updi_link_ldcs(PROGRAMMER * pgm, uint8_t address, uint8_t * value);
+int updi_link_stcs(PROGRAMMER * pgm, uint8_t address, uint8_t value);
+int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
+int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
+int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats);
+int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
+int updi_link_ld(PROGRAMMER * pgm, uint32_t address, uint8_t * value);
+int updi_link_ld16(PROGRAMMER * pgm, uint32_t address, uint16_t * value);
+int updi_link_st(PROGRAMMER * pgm, uint32_t address, uint8_t value);
+int updi_link_st16(PROGRAMMER * pgm, uint32_t address, uint16_t value);
+int updi_link_st_ptr(PROGRAMMER * pgm, uint32_t address);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_link_h */
diff --git a/avrdude/updi_nvm.c b/avrdude/updi_nvm.c
new file mode 100644
index 00000000..df8cc02c
--- /dev/null
+++ b/avrdude/updi_nvm.c
@@ -0,0 +1,1277 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "updi_nvm.h"
+#include "updi_state.h"
+#include "updi_constants.h"
+#include "updi_readwrite.h"
+
+typedef enum 
+{
+  DONT_USE_WORD_ACCESS,
+  USE_WORD_ACCESS
+} access_mode;
+
+#define USE_DEFAULT_COMMAND 0xFF
+
+static int nvm_chip_erase_V0(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def chip_erase(self):
+        """
+        Does a chip erase using the NVM controller
+
+        Note that on locked devices this is not possible
+        and the ERASE KEY has to be used instead, see the unlock method
+        """
+        self.logger.info("Chip erase using NVM CTRL")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before chip erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after chip erase")
+
+        return True
+*/
+  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_flash_page_V0(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
+{
+/*
+    def erase_flash_page(self, address):
+        """
+        Erasing single flash page using the NVM controller (v0)
+
+        :param address: Start address of page to erase
+        :type address: int
+        """
+        self.logger.info("Erase flash page at address 0x%08X", address)
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
+
+        # Dummy write
+        self.readwrite.write_data(address, [0xFF])
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
+*/
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0] = 0xFF;
+  if (updi_write_data(pgm, address, data, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_eeprom_V0(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def erase_eeprom(self):
+        """
+        Erase EEPROM memory only (v0)
+        """
+        self.logger.info("Erase EEPROM")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
+*/  
+  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_user_row_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+/*
+    def erase_user_row(self, address, size):
+        """
+        Erase User Row memory only (v0)
+
+        :param address: Start address of user row
+        :type address: int
+        """
+        self.logger.info("Erase user row")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before user row erase")
+
+        # On this NVM version user row is implemented as EEPROM
+        # When erasing single EEPROM pages a dummy write is needed for each location to be erased
+        for offset in range(size):
+            self.readwrite.write_data(address+offset, [0xFF])
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after user row erase")
+*/
+  uint16_t offset;
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase user row\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0]=0xFF;
+  for (offset = 0; offset<size; offset++)
+  {
+    if (updi_write_data(pgm, address+offset, data, 1) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed at offset 0x%04x\n", progname, offset);
+      return -1;
+    }
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Erase page operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_write_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command);
+
+static int nvm_write_eeprom_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+
+
+static int nvm_write_flash_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_flash(self, address, data):
+        """
+        Writes data to flash (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V0(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
+}
+
+static int nvm_write_user_row_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_user_row(self, address, data):
+        """
+        Writes data to user row (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # On this NVM variant user row is implemented as EEPROM
+        return self.write_eeprom(address, data)
+*/
+  return nvm_write_eeprom_V0(pgm, p, address, buffer, size);
+}
+
+static int nvm_write_eeprom_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_eeprom(self, address, data):
+        """
+        Write data to EEPROM (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=False,
+                              nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE)
+*/
+  return nvm_write_V0(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS, UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE);
+}
+
+static int nvm_write_fuse_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+/*
+    def write_fuse(self, address, data):
+        """
+        Writes one fuse value (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready before fuse write")
+
+        # Write address to NVMCTRL ADDR
+        self.logger.debug("Load NVM address")
+        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL, address & 0xFF)
+        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF)
+
+        # Write data
+        self.logger.debug("Load fuse data")
+        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL, data[0] & 0xFF)
+
+        # Execute
+        self.logger.debug("Execute fuse write")
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE)
+
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after fuse write")
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Load NVM address\n", progname);
+  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_ADDRL, address & 0xFF) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write ADDRL operation failed\n", progname);
+    return -1;
+  }
+  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write ADDRH operation failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Load fuse data\n", progname);
+  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_DATAL, value & 0xFF) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write DATAL operation failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Execute fuse write\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write fuse operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_write_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command)
+{
+/*
+    def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE):
+        """
+        Writes a page of data to NVM (v0)
+
+        By default the PAGE_WRITE command is used, which
+        requires that the page is already erased.
+        By default word access is used (flash)
+
+        :param address: address to write to
+        :param data: data to write
+        :param use_word_access: write whole words?
+        :param nvmcommand: command to use for commit
+        """
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear")
+
+        # Clear the page buffer
+        self.logger.debug("Clear page buffer")
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR)
+
+        # Wait for NVM controller to be ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear")
+
+        # Load the page buffer by writing directly to location
+        if use_word_access:
+            self.readwrite.write_data_words(address, data)
+        else:
+            self.readwrite.write_data(address, data)
+
+        # Write the page to NVM, maybe erase first
+        self.logger.debug("Committing data")
+        self.execute_nvm_command(nvmcommand)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write")
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear page buffer\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (mode == USE_WORD_ACCESS) {
+    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
+      return -1;
+    }
+  } else {
+    if (updi_write_data(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+      return -1;
+    }
+  }
+  avrdude_message(MSG_DEBUG, "%s: Committing data\n", progname);
+  if (nvm_command == USE_DEFAULT_COMMAND) {
+    nvm_command = UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE;
+  }
+  if (updi_nvm_command(pgm, p, nvm_command) < 0) {
+      avrdude_message(MSG_INFO, "%s: Commit data command failed\n", progname);
+      return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_chip_erase_V2(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def chip_erase(self):
+        """
+        Does a chip erase using the NVM controller
+        Note that on locked devices this it not possible
+        and the ERASE KEY has to be used instead
+        """
+        self.logger.info("Chip erase using NVM CTRL")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready before chip erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready after chip erase")
+
+        return True
+*/
+  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_flash_page_V2(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
+{
+/*
+    def erase_flash_page(self, address):
+        """
+        Erasing single flash page using the NVM controller (v1)
+
+        :param address: Start address of page to erase
+        :type address: int
+        """
+        self.logger.info("Erase flash page at address 0x%08X", address)
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
+
+        # Erase command
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE)
+
+        # Dummy write
+        self.readwrite.write_data(address, [0xFF])
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
+
+        # Remove command from NVM controller
+        self.logger.debug("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0] = 0xFF;
+  if (updi_write_data(pgm, address, data, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_eeprom_V2(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def erase_eeprom(self):
+        """
+        Erase EEPROM memory only (v1)
+        """
+        self.logger.info("Erase EEPROM")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
+
+        # Remove command from NVM controller
+        self.logger.debug("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_user_row_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+/*
+    def erase_user_row(self, address, size):
+        """
+        Erase User Row memory only (v1)
+
+        :param address: Start address of user row
+        :type address: int
+        """
+        # size is not used for this NVM version
+        _dummy = size
+        # On this NVM version user row is implemented as flash
+        return self.erase_flash_page(address)
+*/
+  return nvm_erase_flash_page_V2(pgm, p, address);
+}
+
+static int nvm_write_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode);
+
+static int nvm_write_flash_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_flash(self, address, data):
+        """
+        Writes data to flash (v1)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V2(pgm, p, address, buffer, size, USE_WORD_ACCESS);
+}
+
+static int nvm_write_user_row_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_user_row(self, address, data):
+        """
+        Writes data to user row (v1)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # On this NVM variant user row is implemented as Flash
+        return self.write_nvm(address, data, use_word_access=False)
+*/
+  return nvm_write_V2(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS);
+}
+
+static int nvm_write_eeprom_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_eeprom(self, address, data):
+        """
+        Writes data to NVM (EEPROM)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM ready before command write")
+
+        # Write the command to the NVM controller
+        self.logger.info("NVM EEPROM erase/write command")
+        self.execute_nvm_command(nvm_command)
+
+        # Write the data
+        self.readwrite.write_data(address, data)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM ready after data write")
+
+        # Remove command from NVM controller
+        self.logger.info("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: NVM EEPROM erase/write command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_write_data(pgm, address, buffer, size) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear NVM command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_write_fuse_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+/*
+    def write_fuse(self, address, data):
+        """
+        Writes one fuse value
+        V1 fuses are EEPROM-based
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_eeprom(address, data)
+*/
+  unsigned char buffer[1];
+  buffer[0]=value;
+  return nvm_write_eeprom_V2(pgm, p, address, buffer, 1);
+}
+
+static int nvm_write_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode)
+{
+/*
+    def write_nvm(self, address, data, use_word_access):
+        """
+        Writes data to NVM (version 1)
+        This version of the NVM block has no page buffer, so words are written directly.
+
+        :param address: address to write to
+        :param data: data to write
+        :param use_word_access: write in whole words?
+        """
+        nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready before page buffer clear")
+
+        # Write the command to the NVM controller
+        self.logger.info("NVM write command")
+        self.execute_nvm_command(nvm_command)
+
+        # Write the data
+        if use_word_access:
+            self.readwrite.write_data_words(address, data)
+        else:
+            self.readwrite.write_data(address, data)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready after data write")
+
+        # Remove command from NVM controller
+        self.logger.info("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: NVM write command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
+    return -1;
+  }
+  if (mode == USE_WORD_ACCESS) {
+    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
+      return -1;
+    }
+  } else {
+    if (updi_write_data(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+      return -1;
+    }
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear NVM command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_chip_erase_V3(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def chip_erase(self):
+        """
+        Does a chip erase using the NVM controller
+
+        Note that on locked devices this is not possible
+        and the ERASE KEY has to be used instead, see the unlock method
+        """
+        self.logger.info("Chip erase using NVM CTRL")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before chip erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE)
+
+        # And wait for it
+        status = self.wait_nvm_ready()
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+
+        if not status:
+            raise IOError("Timeout waiting for NVM controller to be ready after chip erase")
+
+        return True
+*/
+  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_flash_page_V3(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
+{
+/*
+    def erase_flash_page(self, address):
+        """
+        Erasing single flash page using the NVM controller (v3)
+
+        :param address: Start address of page to erase
+        :type address: int
+        """
+        self.logger.info("Erase flash page at address 0x%08X", address)
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
+
+        # Dummy write
+        self.readwrite.write_data(address, [0xFF])
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE)
+
+        # And wait for it
+        status = self.wait_nvm_ready()
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+
+        if not status:
+            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
+*/  
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0] = 0xFF;
+  if (updi_write_data(pgm, address, data, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_eeprom_V3(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def erase_eeprom(self):
+        """
+        Erase EEPROM memory only
+        """
+        self.logger.info("Erase EEPROM")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE)
+
+        # And wait for it
+        status = self.wait_nvm_ready()
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+
+        if not status:
+            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
+*/
+  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_user_row_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+/*
+    def erase_user_row(self, address, size):
+        """
+        Erase User Row memory only
+
+        :param address: Start address of user row
+        :type address: int
+        """
+        self.logger.info("Erase user row")
+
+        # On this NVM version user row is implemented as FLASH
+        return self.erase_flash_page(self, address)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Erase user row at address 0x%06X\n", progname, address);
+  
+  return nvm_erase_flash_page_V3(pgm, p, address);
+}
+
+static int nvm_write_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command);
+
+static int nvm_write_flash_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_flash(self, address, data):
+        """
+        Writes data to flash (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V3(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
+}
+
+static int nvm_write_user_row_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_user_row(self, address, data):
+        """
+        Writes data to user row (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # On this NVM variant user row is implemented as FLASH
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V3(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
+}
+
+static int nvm_write_eeprom_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_eeprom(self, address, data):
+        """
+        Write data to EEPROM (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=False,
+                              nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE)
+*/
+  return nvm_write_V3(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS, UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE);
+}
+
+static int nvm_write_fuse_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+/*
+    def write_fuse(self, address, data):
+        """
+        Writes one fuse value (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_eeprom(address, data)
+*/
+  unsigned char buffer[1];
+  buffer[0] = value;
+  return nvm_write_eeprom_V3(pgm, p, address, buffer, 1);
+}
+
+static int nvm_write_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command)
+{
+/*
+    def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE):
+        """
+        Writes a page of data to NVM (v3)
+
+        By default the PAGE_WRITE command is used, which
+        requires that the page is already erased.
+        By default word access is used (flash)
+
+        :param address: address to write to
+        :param data: data to write
+        :param use_word_access: write whole words?
+        :param nvmcommand: command to use for commit
+        """
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear")
+
+        # Clear the page buffer
+        self.logger.debug("Clear page buffer")
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR)
+
+        # Wait for NVM controller to be ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear")
+
+        # Load the page buffer by writing directly to location
+        if use_word_access:
+            self.readwrite.write_data_words(address, data)
+        else:
+            self.readwrite.write_data(address, data)
+
+        # Write the page to NVM, maybe erase first
+        self.logger.debug("Committing data")
+        self.execute_nvm_command(nvmcommand)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write")
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear page buffer\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (mode == USE_WORD_ACCESS) {
+    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
+      return -1;
+    }
+  } else {
+    if (updi_write_data(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+      return -1;
+    }
+  }
+  avrdude_message(MSG_DEBUG, "%s: Committing data\n", progname);
+  if (nvm_command == USE_DEFAULT_COMMAND) {
+    nvm_command = UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE;
+  }
+  if (updi_nvm_command(pgm, p, nvm_command) < 0) {
+      avrdude_message(MSG_INFO, "%s: Commit data command failed\n", progname);
+      return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+
+int updi_nvm_chip_erase(PROGRAMMER * pgm, AVRPART * p)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_chip_erase_V0(pgm, p);
+    case UPDI_NVM_MODE_V2:
+      return nvm_chip_erase_V2(pgm, p);
+    case UPDI_NVM_MODE_V3:
+      return nvm_chip_erase_V3(pgm, p);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_erase_flash_page(PROGRAMMER * pgm, AVRPART *p, uint32_t address)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_erase_flash_page_V0(pgm, p, address);
+    case UPDI_NVM_MODE_V2:
+      return nvm_erase_flash_page_V2(pgm, p, address);
+    case UPDI_NVM_MODE_V3:
+      return nvm_erase_flash_page_V3(pgm, p, address);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_erase_eeprom(PROGRAMMER * pgm, AVRPART *p)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_erase_eeprom_V0(pgm, p);
+    case UPDI_NVM_MODE_V2:
+      return nvm_erase_eeprom_V2(pgm, p);
+    case UPDI_NVM_MODE_V3:
+      return nvm_erase_eeprom_V3(pgm, p);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_erase_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_erase_user_row_V0(pgm, p, address, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_erase_user_row_V2(pgm, p, address, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_erase_user_row_V3(pgm, p, address, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_flash(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_flash_V0(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_flash_V2(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_flash_V3(pgm, p, address, buffer, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_user_row_V0(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_user_row_V2(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_user_row_V3(pgm, p, address, buffer, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_eeprom(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_eeprom_V0(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_eeprom_V2(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_eeprom_V3(pgm, p, address, buffer, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_fuse(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_fuse_V0(pgm, p, address, value);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_fuse_V2(pgm, p, address, value);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_fuse_V3(pgm, p, address, value);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p)
+{
+/*
+    def wait_nvm_ready(self):
+        """
+        Waits for the NVM controller to be ready
+        """
+        timeout = Timeout(10000)  # 10 sec timeout, just to be sure
+
+        self.logger.debug("Wait NVM ready")
+        while not timeout.expired():
+            status = self.readwrite.read_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_STATUS)
+            if status & (1 << constants.UPDI_NVM_STATUS_WRITE_ERROR):
+                self.logger.error("NVM error")
+                return False
+
+            if not status & ((1 << constants.UPDI_NVM_STATUS_EEPROM_BUSY) |
+                             (1 << constants.UPDI_NVM_STATUS_FLASH_BUSY)):
+                return True
+
+        self.logger.error("Wait NVM ready timed out")
+        return False
+*/
+  unsigned long start_time;
+  unsigned long current_time;
+  struct timeval tv;
+  uint8_t status;
+  gettimeofday (&tv, NULL);
+  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  do {
+    if (updi_read_byte(pgm, p->nvm_base + UPDI_NVMCTRL_STATUS, &status) < 0){
+      avrdude_message(MSG_INFO, "%s: Status read operation failed\n", progname);
+      return -1;
+    }
+    if (status & (1 << UPDI_NVM_STATUS_WRITE_ERROR)) {
+      avrdude_message(MSG_INFO, "%s: NVM error\n", progname);
+      return -1;
+    }
+    if (!(status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | 
+                    (1 << UPDI_NVM_STATUS_FLASH_BUSY)))) {
+      return 0;
+    }
+    gettimeofday (&tv, NULL);
+    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  } while ((current_time - start_time) < 10000000);
+
+  avrdude_message(MSG_INFO, "%s: Wait NVM ready timed out\n", progname);
+  return -1;
+}
+
+int updi_nvm_command(PROGRAMMER * pgm, AVRPART *p, uint8_t command)
+{
+/*
+    def execute_nvm_command(self, command):
+        """
+        Executes an NVM COMMAND on the NVM CTRL
+
+        :param command: command to execute
+        """
+        self.logger.debug("NVMCMD %d executing", command)
+        return self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_CTRLA, command)
+*/
+  avrdude_message(MSG_DEBUG, "%s: NVMCMD %d executing\n", progname, command);
+
+  return updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_CTRLA, command);
+}
diff --git a/avrdude/updi_nvm.h b/avrdude/updi_nvm.h
new file mode 100644
index 00000000..c0c1544e
--- /dev/null
+++ b/avrdude/updi_nvm.h
@@ -0,0 +1,51 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_nvm_h
+#define updi_nvm_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int updi_nvm_chip_erase(PROGRAMMER * pgm, AVRPART * p);
+int updi_nvm_erase_flash_page(PROGRAMMER * pgm, AVRPART *p, uint32_t address);
+int updi_nvm_erase_eeprom(PROGRAMMER * pgm, AVRPART *p);
+int updi_nvm_erase_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size);
+int updi_nvm_write_flash(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+int updi_nvm_write_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+int updi_nvm_write_eeprom(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+int updi_nvm_write_fuse(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value);
+int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p);
+int updi_nvm_command(PROGRAMMER * pgm, AVRPART *p, uint8_t command);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_nvm_h */
diff --git a/avrdude/updi_readwrite.c b/avrdude/updi_readwrite.c
new file mode 100644
index 00000000..f36015b8
--- /dev/null
+++ b/avrdude/updi_readwrite.c
@@ -0,0 +1,320 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "updi_constants.h"
+#include "updi_link.h"
+#include "updi_readwrite.h"
+
+int updi_read_cs(PROGRAMMER * pgm, uint8_t address, uint8_t * value)
+{
+/*
+    def read_cs(self, address):
+        """
+        Read from Control/Status space
+
+        :param address: address (index) to read
+        :return: value read
+        """
+        return self.datalink.ldcs(address)
+*/
+  return updi_link_ldcs(pgm, address, value);
+}
+
+int updi_write_cs(PROGRAMMER * pgm, uint8_t address, uint8_t value)
+{
+/*
+    def write_cs(self, address, value):
+        """
+        Write to Control/Status space
+
+        :param address: address (index) to write
+        :param value: 8-bit value to write
+        """
+        return self.datalink.stcs(address, value)
+*/
+  return updi_link_stcs(pgm, address, value);
+}
+
+int updi_write_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size)
+{
+/*
+    def write_key(self, size, key):
+        """
+        Write a KEY into UPDI
+
+        :param size: size of key to send
+        :param key: key value
+        """
+        return self.datalink.key(size, key)
+*/
+  return updi_link_key(pgm, buffer, size_type, size);
+}
+
+int updi_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def read_sib(self):
+        """
+        Read the SIB from UPDI
+
+        :return: SIB string (bytearray) read
+        """
+        return self.datalink.read_sib()
+*/
+  return updi_link_read_sib(pgm, buffer, size);
+}
+
+int updi_read_byte(PROGRAMMER * pgm, uint32_t address, uint8_t * value)
+{
+/*
+    def read_byte(self, address):
+        """
+        Read a single byte from UPDI
+
+        :param address: address to read from
+        :return: value read
+        """
+        return self.datalink.ld(address)
+*/
+  return updi_link_ld(pgm, address, value);
+}
+
+int updi_write_byte(PROGRAMMER * pgm, uint32_t address, uint8_t value)
+{
+/*
+    def write_byte(self, address, value):
+        """
+        Writes a single byte to UPDI
+
+        :param address: address to write to
+        :param value: value to write
+        """
+        return self.datalink.st(address, value)
+*/
+  return updi_link_st(pgm, address, value);
+}
+
+int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def read_data(self, address, size):
+        """
+        Reads a number of bytes of data from UPDI
+
+        :param address: address to write to
+        :param size: number of bytes to read
+        """
+        self.logger.debug("Reading %d bytes from 0x%04X", size, address)
+        # Range check
+        if size > constants.UPDI_MAX_REPEAT_SIZE:
+            raise PymcuprogError("Cant read that many bytes in one go")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        if size > 1:
+            self.datalink.repeat(size)
+
+        # Do the read(s)
+        return self.datalink.ld_ptr_inc(size)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Reading %d bytes from 0x%06X", progname, size, address);
+
+  if (size > UPDI_MAX_REPEAT_SIZE) {
+    avrdude_message(MSG_INFO, "%s: Can't read that many bytes in one go\n", progname);
+    return -1;
+  }
+
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+
+  if (size > 1) {
+    if (updi_link_repeat(pgm, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
+      return -1;
+    }
+  }
+  return updi_link_ld_ptr_inc(pgm, buffer, size);
+}
+
+int updi_write_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def write_data(self, address, data):
+        """
+        Writes a number of bytes to memory
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # Special case of 1 byte
+        if len(data) == 1:
+            return self.datalink.st(address, data[0])
+        # Special case of 2 byte
+        if len(data) == 2:
+            self.datalink.st(address, data[0])
+            return self.datalink.st(address + 1, data[1])
+
+        # Range check
+        if len(data) > constants.UPDI_MAX_REPEAT_SIZE:
+            raise PymcuprogError("Invalid length")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        self.datalink.repeat(len(data))
+        return self.datalink.st_ptr_inc(data)
+*/
+  if (size == 1) {
+    return updi_link_st(pgm, address, buffer[0]);
+  }
+  if (size == 2) {
+    if (updi_link_st(pgm, address, buffer[0]) < 0) {
+      avrdude_message(MSG_INFO, "%s: ST operation failed\n", progname);
+      return -1;
+    }
+    return updi_link_st(pgm, address+1, buffer[1]);
+  }
+  if (size > UPDI_MAX_REPEAT_SIZE) {
+    avrdude_message(MSG_INFO, "%s: Invalid length\n", progname);
+    return -1;
+  }
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+  if (updi_link_repeat(pgm, size) < 0) {
+    avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
+    return -1;
+  }
+  return updi_link_st_ptr_inc(pgm, buffer, size);
+}
+
+int updi_read_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def read_data_words(self, address, words):
+        """
+        Reads a number of words of data from UPDI
+
+        :param address: address to write to
+        :param words: number of words to read
+        """
+        self.logger.debug("Reading %d words from 0x%04X", words, address)
+
+        # Range check
+        if words > constants.UPDI_MAX_REPEAT_SIZE >> 1:
+            raise PymcuprogError("Cant read that many words in one go")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        if words > 1:
+            self.datalink.repeat(words)
+
+        # Do the read
+        return self.datalink.ld_ptr_inc16(words)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Reading %d words from 0x%06X", progname, size, address);
+
+  if (size > (UPDI_MAX_REPEAT_SIZE >> 1)) {
+    avrdude_message(MSG_INFO, "%s: Can't read that many words in one go\n", progname);
+    return -1;
+  }
+
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+
+  if (size > 1) {
+    if (updi_link_repeat(pgm, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
+      return -1;
+    }
+  }
+  return updi_link_ld_ptr_inc16(pgm, buffer, size);
+}
+
+int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def write_data_words(self, address, data):
+        """
+        Writes a number of words to memory
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # Special-case of 1 word
+        if len(data) == 2:
+            value = data[0] + (data[1] << 8)
+            return self.datalink.st16(address, value)
+
+        # Range check
+        if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1:
+            raise PymcuprogError("Invalid length")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        self.datalink.repeat(len(data) >> 1)
+        return self.datalink.st_ptr_inc16(data)
+*/
+  if (size == 2) {
+    return updi_link_st16(pgm, address, buffer[0] + (buffer[1] << 8));
+  }
+  if (size > UPDI_MAX_REPEAT_SIZE << 1) {
+    avrdude_message(MSG_INFO, "%s: Invalid length\n", progname);
+    return -1;
+  }
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+  if (updi_link_repeat(pgm, size >> 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
+    return -1;
+  }
+  return updi_link_st_ptr_inc16(pgm, buffer, size);
+}
diff --git a/avrdude/updi_readwrite.h b/avrdude/updi_readwrite.h
new file mode 100644
index 00000000..9519d179
--- /dev/null
+++ b/avrdude/updi_readwrite.h
@@ -0,0 +1,51 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_readwrite_h
+#define updi_readwrite_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int updi_read_cs(PROGRAMMER * pgm, uint8_t address, uint8_t * value);
+int updi_write_cs(PROGRAMMER * pgm, uint8_t address, uint8_t value);
+int updi_write_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
+int updi_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_read_byte(PROGRAMMER * pgm, uint32_t address, uint8_t * value);
+int updi_write_byte(PROGRAMMER * pgm, uint32_t address, uint8_t value);
+int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+int updi_write_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+int updi_read_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_readwrite_h */
diff --git a/avrdude/updi_state.c b/avrdude/updi_state.c
new file mode 100644
index 00000000..63d80f46
--- /dev/null
+++ b/avrdude/updi_state.c
@@ -0,0 +1,55 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+
+#include "libavrdude.h"
+#include "updi_state.h"
+
+updi_sib_info* updi_get_sib_info(PROGRAMMER * pgm)
+{
+  return &((updi_state *)(pgm->cookie))->sib_info;
+}
+
+updi_datalink_mode updi_get_datalink_mode(PROGRAMMER * pgm)
+{
+  return ((updi_state *)(pgm->cookie))->datalink_mode;
+}
+
+void updi_set_datalink_mode(PROGRAMMER * pgm, updi_datalink_mode mode)
+{
+  ((updi_state *)(pgm->cookie))->datalink_mode = mode;
+}
+
+updi_nvm_mode updi_get_nvm_mode(PROGRAMMER * pgm)
+{
+  return ((updi_state *)(pgm->cookie))->nvm_mode;
+}
+
+void updi_set_nvm_mode(PROGRAMMER * pgm, updi_nvm_mode mode)
+{
+  ((updi_state *)(pgm->cookie))->nvm_mode = mode;
+}
diff --git a/avrdude/updi_state.h b/avrdude/updi_state.h
new file mode 100644
index 00000000..d4e39d4c
--- /dev/null
+++ b/avrdude/updi_state.h
@@ -0,0 +1,85 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_state_h
+#define updi_state_h
+
+#include "libavrdude.h"
+
+typedef enum
+{
+  UPDI_LINK_MODE_16BIT,
+  UPDI_LINK_MODE_24BIT
+} updi_datalink_mode;
+
+typedef enum
+{
+  UPDI_NVM_MODE_V0,
+  UPDI_NVM_MODE_V2,
+  UPDI_NVM_MODE_V3
+} updi_nvm_mode;
+
+#define SIB_INFO_STRING_LENGTH 32
+#define SIB_INFO_FAMILY_LENGTH 8
+#define SIB_INFO_NVM_LENGTH    3
+#define SIB_INFO_DEBUG_LENGTH  3
+#define SIB_INFO_PDI_LENGTH    4
+#define SIB_INFO_EXTRA_LENGTH  20
+
+typedef struct 
+{
+  unsigned char sib_string[SIB_INFO_STRING_LENGTH+1];
+  char family_string[SIB_INFO_FAMILY_LENGTH+1];
+  char nvm_string[SIB_INFO_NVM_LENGTH+1];
+  char debug_string[SIB_INFO_DEBUG_LENGTH+1];
+  char pdi_string[SIB_INFO_PDI_LENGTH+1];
+  char extra_string[SIB_INFO_EXTRA_LENGTH+1];
+  char nvm_version;
+  char debug_version;
+} updi_sib_info;
+
+typedef struct
+{
+  updi_sib_info sib_info;
+  updi_datalink_mode datalink_mode;
+  updi_nvm_mode nvm_mode;
+} updi_state;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+updi_sib_info* updi_get_sib_info(PROGRAMMER * pgm);
+updi_datalink_mode updi_get_datalink_mode(PROGRAMMER * pgm);
+void updi_set_datalink_mode(PROGRAMMER * pgm, updi_datalink_mode mode);
+updi_nvm_mode updi_get_nvm_mode(PROGRAMMER * pgm);
+void updi_set_nvm_mode(PROGRAMMER * pgm, updi_nvm_mode mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_state_h */
diff --git a/pgm_type.c b/pgm_type.c
index 9f28f441..becf7bdd 100644
--- a/pgm_type.c
+++ b/pgm_type.c
@@ -45,6 +45,7 @@
 #include "pickit2.h"
 #include "ppi.h"
 #include "serbb.h"
+#include "serialupdi.h"
 #include "stk500.h"
 #include "stk500generic.h"
 #include "stk500v2.h"
@@ -87,6 +88,7 @@ const PROGRAMMER_TYPE programmers_types[] = {
         {"par", par_initpgm, par_desc},
         {"pickit2", pickit2_initpgm, pickit2_desc},
         {"serbb", serbb_initpgm, serbb_desc},
+        {"serialupdi", serialupdi_initpgm, serialupdi_desc},
         {"stk500", stk500_initpgm, stk500_desc},
         {"stk500generic", stk500generic_initpgm, stk500generic_desc},
         {"stk500v2", stk500v2_initpgm, stk500v2_desc},

From 8f67f9c50bb2899a679f1724ea78e479f38ab491 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Wed, 8 Dec 2021 14:18:21 +0000
Subject: [PATCH 03/12] Implemented byte and page read operations

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1514 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c     | 26 ++++++++++++++++++++------
 avrdude/updi_readwrite.c |  2 +-
 2 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 0f4e70c1..5da521ea 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -195,19 +195,33 @@ static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
 static int serialupdi_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, 
                                 unsigned long addr, unsigned char * value)
 {
-//  avrdude_message(MSG_INFO, "%s: error: read byte not implemented yet\n",
-//    	            progname);
   return updi_read_byte(pgm, mem->offset + addr, value);
-//  return -1;
 }
 
 static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
                                  unsigned int page_size,
                                  unsigned int addr, unsigned int n_bytes)
 {
-  avrdude_message(MSG_INFO, "%s: error: paged load not implemented yet\n",
-    	    progname);
-  return -1;
+  if (n_bytes > m->readsize) {
+    unsigned int read_offset = addr;
+    unsigned int remaining_bytes = n_bytes;
+    int read_bytes = 0;
+    int rc;
+    while (remaining_bytes > 0) {
+      rc = updi_read_data(pgm, m->offset + read_offset, m->buf + read_offset, m->readsize);
+      if (rc < 0) {
+        avrdude_message(MSG_INFO, "%s: Paged load operation failed\n", progname);
+        return rc;
+      } else {
+        read_bytes+=rc;
+        read_offset+=m->readsize;
+        remaining_bytes-=m->readsize;
+      }
+    }
+    return read_bytes;
+  } else {
+    return updi_read_data(pgm, m->offset + addr, m->buf, n_bytes);
+  }
 }
 
 static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
diff --git a/avrdude/updi_readwrite.c b/avrdude/updi_readwrite.c
index f36015b8..eecfb7f0 100644
--- a/avrdude/updi_readwrite.c
+++ b/avrdude/updi_readwrite.c
@@ -153,7 +153,7 @@ int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_
         # Do the read(s)
         return self.datalink.ld_ptr_inc(size)
 */
-  avrdude_message(MSG_DEBUG, "%s: Reading %d bytes from 0x%06X", progname, size, address);
+  avrdude_message(MSG_DEBUG, "%s: Reading %d bytes from 0x%06X\n", progname, size, address);
 
   if (size > UPDI_MAX_REPEAT_SIZE) {
     avrdude_message(MSG_INFO, "%s: Can't read that many bytes in one go\n", progname);

From c6902553be5a102091a626a4b154278b696cceb7 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Sat, 11 Dec 2021 22:22:38 +0000
Subject: [PATCH 04/12] First successful programming

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1516 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c | 401 +++++++++++++++++++++++++++++++++++++++++--
 avrdude/updi_nvm.c   |  20 +--
 2 files changed, 392 insertions(+), 29 deletions(-)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 5da521ea..2db77875 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -40,6 +40,11 @@
 #include "updi_link.h"
 #include "updi_state.h"
 #include "updi_readwrite.h"
+#include "updi_nvm.h"
+#include "updi_constants.h"
+
+static int serialupdi_enter_progmode(PROGRAMMER * pgm);
+static int serialupdi_leave_progmode(PROGRAMMER * pgm);
 
 static void serialupdi_setup(PROGRAMMER * pgm)
 {
@@ -64,7 +69,7 @@ static int serialupdi_open(PROGRAMMER * pgm, char * port)
   return updi_link_open(pgm);
 }
 
-static int serialupdi_decode_sib(updi_sib_info * sib_info)
+static int serialupdi_decode_sib(PROGRAMMER * pgm, updi_sib_info * sib_info)
 {
   char * str_ptr;
 
@@ -105,12 +110,18 @@ static int serialupdi_decode_sib(updi_sib_info * sib_info)
   switch (sib_info->nvm_version) {
     case '0':
       avrdude_message(MSG_INFO, "%s: NVM type 0: 16-bit, page oriented write\n", progname);
+      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V0);
+      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
       break;
     case '2':
       avrdude_message(MSG_INFO, "%s: NVM type 2: 24-bit, word oriented write\n", progname);
+      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V2);
+      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_24BIT);
       break;
     case '3':
       avrdude_message(MSG_INFO, "%s: NVM type 3: 16-bit, page oriented\n", progname);
+      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V3);
+      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
       break;
     default:
       avrdude_message(MSG_INFO, "%s: Unsupported NVM type: %c, please update software\n", progname, sib_info->nvm_version);
@@ -121,9 +132,240 @@ static int serialupdi_decode_sib(updi_sib_info * sib_info)
 
 static void serialupdi_close(PROGRAMMER * pgm)
 {
+  if (serialupdi_leave_progmode(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: Unable to leave NVM programming mode\n", progname);
+  }
   updi_link_close(pgm);
 }
 
+typedef enum {
+  APPLY_RESET,
+  RELEASE_RESET
+} reset_mode;
+
+static int serialupdi_reset(PROGRAMMER * pgm, reset_mode mode)
+{
+/*
+    def reset(self, apply_reset):
+        """
+        Applies or releases an UPDI reset condition
+
+        :param apply_reset: True to apply, False to release
+        """
+        if apply_reset:
+            self.logger.info("Apply reset")
+            self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, constants.UPDI_RESET_REQ_VALUE)
+        else:
+            self.logger.info("Release reset")
+            self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, 0x00)
+*/
+  switch (mode) {
+    case APPLY_RESET:
+      avrdude_message(MSG_DEBUG, "%s: Sending reset request\n", progname);
+      return updi_write_cs(pgm, UPDI_ASI_RESET_REQ, UPDI_RESET_REQ_VALUE);
+    case RELEASE_RESET:
+      avrdude_message(MSG_DEBUG, "%s: Sending release reset request\n", progname);
+      return updi_write_cs(pgm, UPDI_ASI_RESET_REQ, 0x00);
+  }
+  return -1;
+}
+
+static int serialupdi_wait_for_unlock(PROGRAMMER * pgm, unsigned int ms) {
+/*
+    def wait_unlocked(self, timeout_ms):
+        """
+        Waits for the device to be unlocked.
+        All devices boot up as locked until proven otherwise
+
+        :param timeout_ms: number of milliseconds to wait
+        """
+        timeout = Timeout(timeout_ms)
+
+        while not timeout.expired():
+            if not self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & (
+                    1 << constants.UPDI_ASI_SYS_STATUS_LOCKSTATUS):
+                return True
+
+        self.logger.error("Timeout waiting for device to unlock")
+        return False
+*/  
+  unsigned long start_time;
+  unsigned long current_time;
+  struct timeval tv;
+  uint8_t status;
+  gettimeofday (&tv, NULL);
+  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  do {
+    if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &status) >= 0) {
+      if (!(status & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS))) {
+        return 0;
+      }
+    }
+    gettimeofday (&tv, NULL);
+    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  } while ((current_time - start_time) < (ms * 1000));
+
+  avrdude_message(MSG_INFO, "%s: Timeout waiting for device to unlock\n", progname);
+  return -1;
+}
+
+static int serialupdi_in_prog_mode(PROGRAMMER * pgm, uint8_t * in_prog_mode)
+{
+/*
+    def in_prog_mode(self):
+        """
+        Checks whether the NVM PROG flag is up
+        """
+        if self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & (1 << constants.UPDI_ASI_SYS_STATUS_NVMPROG):
+            return True
+        return False
+*/
+  uint8_t value;
+  int rc;
+  
+  rc = updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value);
+  
+  if (rc < 0) {
+    avrdude_message(MSG_INFO, "%s: Read CS operation failed\n", progname);
+    return rc;
+  }
+
+  if (value & (1 << UPDI_ASI_SYS_STATUS_NVMPROG)) {
+    *in_prog_mode = 1;
+  } else {
+    *in_prog_mode = 0;
+  }
+  return 0;
+}
+
+static int serialupdi_enter_progmode(PROGRAMMER * pgm)
+{
+/*
+def enter_progmode(self):
+        """
+        Enters into NVM programming mode
+        """
+        # First check if NVM is already enabled
+        if self.in_prog_mode():
+            self.logger.info("Already in NVM programming mode")
+            return True
+
+        self.logger.info("Entering NVM programming mode")
+
+        # Put in the key
+        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_NVM)
+
+        # Check key status
+        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
+        self.logger.debug("Key status = 0x%02X", key_status)
+
+        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_NVMPROG):
+            self.logger.error("Key status = 0x%02X", key_status)
+            raise IOError("Key not accepted")
+
+        # Toggle reset
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+
+        # And wait for unlock
+        if not self.wait_unlocked(100):
+            raise IOError("Failed to enter NVM programming mode: device is locked")
+
+        # Check for NVMPROG flag
+        if not self.in_prog_mode():
+            raise IOError("Failed to enter NVM programming mode")
+
+        self.logger.debug("Now in NVM programming mode")
+        return True
+*/
+  uint8_t in_prog_mode;
+  unsigned char buffer[8];
+  uint8_t key_status;
+
+  if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed\n", progname);
+    return -1;
+  }
+  if (in_prog_mode) {
+    avrdude_message(MSG_DEBUG, "%s: Already in prog mode\n", progname);
+    return 0;
+  }
+  avrdude_message(MSG_INFO, "%s: Entering NVM programming mode\n", progname);
+  
+  memcpy(buffer, UPDI_KEY_NVM, sizeof(buffer));
+  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Writing NVM KEY failed\n", progname);
+    return -1;
+  }
+
+  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
+
+  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_NVMPROG))) {
+    avrdude_message(MSG_INFO, "%s: Key was not accepted\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_wait_for_unlock(pgm, 100) < 0) {
+    avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode: device is locked\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed\n", progname);
+    return -1;
+  }
+
+  if (!in_prog_mode) {
+    avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode\n", progname);
+    return -1;
+  }
+
+  avrdude_message(MSG_DEBUG, "%s: Entered NVM programming mode\n", progname);
+  return 0;
+}
+
+static int serialupdi_leave_progmode(PROGRAMMER * pgm)
+{
+/*
+    def leave_progmode(self):
+        """
+        Disables UPDI which releases any keys enabled
+        """
+        self.logger.info("Leaving NVM programming mode")
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+        self.readwrite.write_cs(constants.UPDI_CS_CTRLB,
+                                (1 << constants.UPDI_CTRLB_UPDIDIS_BIT) | (1 << constants.UPDI_CTRLB_CCDETDIS_BIT))
+*/
+  avrdude_message(MSG_INFO, "%s: Leaving NVM programming mode\n", progname);
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  return updi_write_cs(pgm, UPDI_CS_CTRLB, (1 << UPDI_CTRLB_UPDIDIS_BIT) | (1 << UPDI_CTRLB_CCDETDIS_BIT));
+}
+
 static int serialupdi_initialize(PROGRAMMER * pgm, AVRPART * p)
 {
   updi_sib_info * sib_info = updi_get_sib_info(pgm);
@@ -137,17 +379,15 @@ static int serialupdi_initialize(PROGRAMMER * pgm, AVRPART * p)
     avrdude_message(MSG_INFO, "%s: Read SIB operation failed\n", progname);
     return -1;
   }
-  if (serialupdi_decode_sib(sib_info) < 0) {
+  if (serialupdi_decode_sib(pgm, sib_info) < 0) {
     avrdude_message(MSG_INFO, "%s: Decode SIB_INFO failed\n", progname);
     return -1;
   }
-  updi_set_nvm_mode(pgm, sib_info->nvm_version);
-  if (sib_info->nvm_version == '2') {
-    updi_set_datalink_mode(pgm, UPDI_LINK_MODE_24BIT);
-  } else {
-    updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
+  if (serialupdi_enter_progmode(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
+    return -1;
   }
-  
+
   return 0;
 }
 
@@ -185,19 +425,19 @@ static int serialupdi_program_enable(PROGRAMMER * pgm, AVRPART * p)
   return -1;
 }
 
-static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
-{
-  avrdude_message(MSG_INFO, "%s: error: chip erase not implemented yet\n",
-    	    progname);
-  return -1;
-}
-
 static int serialupdi_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, 
                                 unsigned long addr, unsigned char * value)
 {
   return updi_read_byte(pgm, mem->offset + addr, value);
 }
 
+static int serialupdi_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
+                                 unsigned long addr, unsigned char value)
+{
+  return updi_write_byte(pgm, mem->offset + addr, value);
+}
+
+
 static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
                                  unsigned int page_size,
                                  unsigned int addr, unsigned int n_bytes)
@@ -208,7 +448,8 @@ static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
     int read_bytes = 0;
     int rc;
     while (remaining_bytes > 0) {
-      rc = updi_read_data(pgm, m->offset + read_offset, m->buf + read_offset, m->readsize);
+      rc = updi_read_data(pgm, m->offset + read_offset, m->buf + read_offset, 
+                          remaining_bytes > m->readsize ? m->readsize : remaining_bytes);
       if (rc < 0) {
         avrdude_message(MSG_INFO, "%s: Paged load operation failed\n", progname);
         return rc;
@@ -228,11 +469,133 @@ static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
                                   unsigned int page_size,
                                   unsigned int addr, unsigned int n_bytes)
 {
-  avrdude_message(MSG_INFO, "%s: error: paged write not implemented yet\n",
+  int rc;
+
+  if (serialupdi_enter_progmode(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
+    return -1;
+  }
+
+  if (n_bytes > m->page_size) {
+    unsigned int write_offset = addr;
+    unsigned int remaining_bytes = n_bytes;
+    int write_bytes = 0;
+    while (remaining_bytes > 0) {
+
+      if (strcmp(m->desc, "eeprom")==0) {
+        rc = updi_nvm_write_eeprom(pgm, p, m->offset + write_offset, m->buf + write_offset, 
+                                   remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
+      } else if (strcmp(m->desc, "flash")==0) {
+        rc = updi_nvm_write_flash(pgm, p, m->offset + write_offset, m->buf + write_offset, 
+                                  remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
+      } else {
+        rc = -1;
+      }
+
+      if (rc < 0) {
+        avrdude_message(MSG_INFO, "%s: Paged write operation failed\n", progname);
+        return rc;
+      } else {
+        write_bytes+=rc;
+        write_offset+=m->page_size;
+        remaining_bytes-=m->page_size;
+      }
+    }
+    return write_bytes;
+  } else {
+    if (strcmp(m->desc, "eeprom")==0) {
+      rc = updi_nvm_write_eeprom(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
+    } else if (strcmp(m->desc, "flash")==0) {
+      rc = updi_nvm_write_flash(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
+    } else {
+      rc = -1;
+    }
+    return rc;
+  }
+}
+
+static int serialupdi_unlock(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def unlock(self):
+        """
+        Unlock by chip erase
+        """
+        # Put in the key
+        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE)
+
+        # Check key status
+        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
+        self.logger.debug("Key status = 0x%02X", key_status)
+
+        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE):
+            raise PymcuprogError("Key not accepted")
+
+        # Toggle reset
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+
+        # And wait for unlock
+        if not self.wait_unlocked(500):
+            raise PymcuprogError("Failed to chip erase using key")
+*/
+  unsigned char buffer[8];
+  uint8_t key_status;
+  
+  memcpy(buffer, UPDI_KEY_CHIPERASE, sizeof(buffer));
+
+  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Writing NVM KEY failed\n", progname);
+    return -1;
+  }
+
+  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
+
+  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_CHIPERASE))) {
+    avrdude_message(MSG_INFO, "%s: Key not accepted\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  return serialupdi_wait_for_unlock(pgm, 500);
+}
+
+static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
+{
+  if (serialupdi_enter_progmode(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
+    return -1;
+  }
+
+  if (updi_nvm_chip_erase(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase failed, device might be locked, attempting unlock now\n", progname);
+    return serialupdi_unlock(pgm, p);
+  }
+  return 0;
+}
+
+static int serialupdi_page_erase(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                 unsigned int baseaddr)
+{
+  avrdude_message(MSG_INFO, "%s: error: page erase not implemented yet\n",
     	    progname);
   return -1;
 }
 
+
 void serialupdi_initpgm(PROGRAMMER * pgm)
 {
   strcpy(pgm->type, "serialupdi");
@@ -251,14 +614,16 @@ void serialupdi_initpgm(PROGRAMMER * pgm)
   pgm->open           = serialupdi_open;
   pgm->close          = serialupdi_close;
   pgm->read_byte      = serialupdi_read_byte;
-  pgm->write_byte     = avr_write_byte_default;
+  pgm->write_byte     = serialupdi_write_byte;
 
   /*
    * optional functions
    */
 
+  pgm->unlock         = serialupdi_unlock;
   pgm->paged_write    = serialupdi_paged_write;
   pgm->paged_load     = serialupdi_paged_load;
+  pgm->page_erase     = serialupdi_page_erase;
   pgm->setup          = serialupdi_setup;
   pgm->teardown       = serialupdi_teardown;
 
diff --git a/avrdude/updi_nvm.c b/avrdude/updi_nvm.c
index df8cc02c..f3d899a9 100644
--- a/avrdude/updi_nvm.c
+++ b/avrdude/updi_nvm.c
@@ -1239,17 +1239,15 @@ int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p)
   gettimeofday (&tv, NULL);
   start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
   do {
-    if (updi_read_byte(pgm, p->nvm_base + UPDI_NVMCTRL_STATUS, &status) < 0){
-      avrdude_message(MSG_INFO, "%s: Status read operation failed\n", progname);
-      return -1;
-    }
-    if (status & (1 << UPDI_NVM_STATUS_WRITE_ERROR)) {
-      avrdude_message(MSG_INFO, "%s: NVM error\n", progname);
-      return -1;
-    }
-    if (!(status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | 
-                    (1 << UPDI_NVM_STATUS_FLASH_BUSY)))) {
-      return 0;
+    if (updi_read_byte(pgm, p->nvm_base + UPDI_NVMCTRL_STATUS, &status) >= 0) {
+      if (status & (1 << UPDI_NVM_STATUS_WRITE_ERROR)) {
+        avrdude_message(MSG_INFO, "%s: NVM error\n", progname);
+        return -1;
+      }
+      if (!(status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | 
+                      (1 << UPDI_NVM_STATUS_FLASH_BUSY)))) {
+        return 0;
+      }
     }
     gettimeofday (&tv, NULL);
     current_time = (tv.tv_sec * 1000000) + tv.tv_usec;

From e941d4d3f1796f7bca429c5ea8c7e2337304c21a Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Sun, 12 Dec 2021 20:00:23 +0000
Subject: [PATCH 05/12] Implemented faster flash programming method

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1517 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c     |   2 +
 avrdude/updi_link.c      | 103 +++++++++++++++++++++++++++++++++++++++
 avrdude/updi_link.h      |   1 +
 avrdude/updi_readwrite.c |   6 +--
 4 files changed, 107 insertions(+), 5 deletions(-)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 2db77875..98b09b32 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -489,6 +489,7 @@ static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
         rc = updi_nvm_write_flash(pgm, p, m->offset + write_offset, m->buf + write_offset, 
                                   remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
       } else {
+        avrdude_message(MSG_INFO, "%s: Invalid memory type: <%s:%d>, 0x%06X, %d (0x%04X)\n", progname, m->desc, page_size, addr, n_bytes, n_bytes);
         rc = -1;
       }
 
@@ -508,6 +509,7 @@ static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
     } else if (strcmp(m->desc, "flash")==0) {
       rc = updi_nvm_write_flash(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
     } else {
+      avrdude_message(MSG_INFO, "%s: Invalid memory type: <%s:%d>, 0x%06X, %d (0x%04X)\n", progname, m->desc, page_size, addr, n_bytes, n_bytes);
       rc = -1;
     }
     return rc;
diff --git a/avrdude/updi_link.c b/avrdude/updi_link.c
index 33c3da1d..a3852bfe 100644
--- a/avrdude/updi_link.c
+++ b/avrdude/updi_link.c
@@ -526,6 +526,109 @@ int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t wo
   return 0;
 }
 
+int updi_link_st_ptr_inc16_RSD(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words, int blocksize) {
+/*
+    def st_ptr_inc16_RSD(self, data, blocksize):
+        """
+        Store a 16-bit word value to the pointer location with pointer post-increment
+        :param data: data to store
+        :blocksize: max number of bytes being sent -1 for all.
+                    Warning: This does not strictly honor blocksize for values < 6
+                    We always glob together the STCS(RSD) and REP commands.
+                    But this should pose no problems for compatibility, because your serial adapter can't deal with 6b chunks,
+                    none of pymcuprog would work!
+        """
+        self.logger.debug("ST16 to *ptr++ with RSD, data length: 0x%03X in blocks of:  %d", len(data), blocksize)
+
+        #for performance we glob everything together into one USB transfer....
+        repnumber= ((len(data) >> 1) -1)
+        data = [*data, *[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA, 0x06]]
+
+        if blocksize == -1 :
+            # Send whole thing at once stcs + repeat + st + (data + stcs)
+            blocksize = 3 + 3 + 2 + len(data)
+        num = 0
+        firstpacket = []
+        if blocksize < 10 :
+            # very small block size - we send pair of 2-byte commands first.
+            firstpacket = [*[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA, 0x0E],
+                            *[constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, (repnumber & 0xFF)]]
+            data = [*[constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |constants.UPDI_DATA_16], *data]
+            num = 0
+        else:
+            firstpacket = [*[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA , 0x0E],
+                            *[constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, (repnumber & 0xFF)],
+                            *[constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_16],
+                            *data[:blocksize - 8]]
+            num = blocksize - 8
+        self.updi_phy.send( firstpacket )
+
+        # if finite block size, this is used.
+        while num < len(data):
+            data_slice = data[num:num+blocksize]
+            self.updi_phy.send(data_slice)
+            num += len(data_slice)
+*/
+  avrdude_message(MSG_DEBUG, "%s: ST16 to *ptr++ with RSD, data length: 0x%03X in blocks of: %d\n", progname, words * 2, blocksize);
+
+  unsigned int temp_buffer_size = 3 + 3 + 2 + (words * 2) + 3;
+  unsigned int num=0;
+  unsigned char* temp_buffer = malloc(temp_buffer_size);
+
+  if (temp_buffer == 0) {
+    avrdude_message(MSG_INFO, "%s: Allocating temporary buffer failed\n", progname);
+    return -1;
+  }
+
+  if (blocksize == -1) {
+    blocksize = temp_buffer_size;
+  }
+
+  temp_buffer[0] = UPDI_PHY_SYNC;
+  temp_buffer[1] = UPDI_STCS | UPDI_CS_CTRLA;
+  temp_buffer[2] = 0x0E;
+  temp_buffer[3] = UPDI_PHY_SYNC;
+  temp_buffer[4] = UPDI_REPEAT | UPDI_REPEAT_BYTE;
+  temp_buffer[5] = (words - 1) & 0xFF;
+  temp_buffer[6] = UPDI_PHY_SYNC;
+  temp_buffer[7] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16;
+
+  memcpy(temp_buffer + 8, buffer, words * 2);
+
+  temp_buffer[temp_buffer_size-3] = UPDI_PHY_SYNC;
+  temp_buffer[temp_buffer_size-2] = UPDI_STCS | UPDI_CS_CTRLA;
+  temp_buffer[temp_buffer_size-1] = 0x06;
+
+  if (blocksize < 10) {
+    if (updi_physical_send(pgm, temp_buffer, 6) < 0) {
+      avrdude_message(MSG_INFO, "%s: Failed to send first package\n", progname);
+      free(temp_buffer);
+      return -1;
+    }
+    num = 6;
+  } 
+
+  while (num < temp_buffer_size) {
+    int next_package_size;
+
+    if (num + blocksize > temp_buffer_size) {
+      next_package_size = temp_buffer_size - num;
+    } else {
+      next_package_size = blocksize;
+    }
+
+    if (updi_physical_send(pgm, temp_buffer + num, next_package_size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Failed to send package\n", progname);
+      free(temp_buffer);
+      return -1;
+    }
+
+    num+=next_package_size;
+  }
+  free(temp_buffer);
+  return 0;
+}
+
 int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats)
 {
 /*
diff --git a/avrdude/updi_link.h b/avrdude/updi_link.h
index e6848b0d..5b5e5bda 100644
--- a/avrdude/updi_link.h
+++ b/avrdude/updi_link.h
@@ -42,6 +42,7 @@ int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size
 int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
 int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
 int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
+int updi_link_st_ptr_inc16_RSD(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words, int blocksize);
 int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats);
 int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
 int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
diff --git a/avrdude/updi_readwrite.c b/avrdude/updi_readwrite.c
index eecfb7f0..e304a510 100644
--- a/avrdude/updi_readwrite.c
+++ b/avrdude/updi_readwrite.c
@@ -312,9 +312,5 @@ int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer,
     avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
     return -1;
   }
-  if (updi_link_repeat(pgm, size >> 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
-    return -1;
-  }
-  return updi_link_st_ptr_inc16(pgm, buffer, size);
+  return updi_link_st_ptr_inc16_RSD(pgm, buffer, size >> 1, -1);
 }

From 9ff14b7a4211ad114f126b8347a45106a5127fd8 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Mon, 13 Dec 2021 08:49:29 +0000
Subject: [PATCH 06/12] Fix candidate for issue with atmega4809

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1518 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 98b09b32..2aa0252a 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -461,7 +461,7 @@ static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
     }
     return read_bytes;
   } else {
-    return updi_read_data(pgm, m->offset + addr, m->buf, n_bytes);
+    return updi_read_data(pgm, m->offset + addr, m->buf + addr, n_bytes);
   }
 }
 

From 189f829c3f5b6d19c5a493a119543d1f9c262935 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Mon, 13 Dec 2021 10:59:46 +0000
Subject: [PATCH 07/12] Fix candidate for write fuse operation

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1519 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 2aa0252a..15ee3275 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -434,6 +434,9 @@ static int serialupdi_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
 static int serialupdi_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
                                  unsigned long addr, unsigned char value)
 {
+  if (strstr(mem->desc, "fuse") != 0) {
+    return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
+  }
   return updi_write_byte(pgm, mem->offset + addr, value);
 }
 

From dc846ba7e864b9bf6218559ff21bb2e0ea3b4844 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Mon, 13 Dec 2021 11:11:27 +0000
Subject: [PATCH 08/12] Fix candidate for EEPROM writing issue

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1520 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 15ee3275..4b3a32a9 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -437,6 +437,11 @@ static int serialupdi_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
   if (strstr(mem->desc, "fuse") != 0) {
     return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
   }
+  if (strcmp(mem->desc, "eeprom") == 0) {
+    unsigned char buffer[1];
+    buffer[0]=value;
+    return updi_nvm_write_eeprom(pgm, p, mem->offset + addr, buffer, 1);
+  }
   return updi_write_byte(pgm, mem->offset + addr, value);
 }
 

From c3100763cb8ca6e9bf743e95f5e1bf27474fbdc1 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Wed, 15 Dec 2021 14:00:14 +0000
Subject: [PATCH 09/12] Implemented lockbits programming and forced chip erase
 procedure

git-svn-id: svn://svn.savannah.nongnu.org/avrdude/branches/serialupdi@1522 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 avrdude/serialupdi.c | 70 +++++++++++++++++++++++++++++---------------
 1 file changed, 46 insertions(+), 24 deletions(-)

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
index 4b3a32a9..62a9d44c 100644
--- a/avrdude/serialupdi.c
+++ b/avrdude/serialupdi.c
@@ -238,6 +238,8 @@ static int serialupdi_in_prog_mode(PROGRAMMER * pgm, uint8_t * in_prog_mode)
   return 0;
 }
 
+static int serialupdi_unlock(PROGRAMMER * pgm, AVRPART * p);
+
 static int serialupdi_enter_progmode(PROGRAMMER * pgm)
 {
 /*
@@ -283,8 +285,20 @@ def enter_progmode(self):
   uint8_t key_status;
 
   if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
-    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed\n", progname);
-    return -1;
+    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed, attempting reset\n", progname);
+    if (serialupdi_leave_progmode(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: Unable to leave progmode\n", progname);
+      return -1;
+    }
+    if (updi_link_init(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
+      return -1;
+    }
+    avrdude_message(MSG_INFO, "%s: UPDI link initialization OK\n", progname);
+    if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
+      avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed again, exiting\n", progname);
+      return -1;
+    }
   }
   if (in_prog_mode) {
     avrdude_message(MSG_DEBUG, "%s: Already in prog mode\n", progname);
@@ -321,7 +335,16 @@ def enter_progmode(self):
 
   if (serialupdi_wait_for_unlock(pgm, 100) < 0) {
     avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode: device is locked\n", progname);
-    return -1;
+    if (!ovsigck) {
+      return -1;
+    }
+    if (serialupdi_unlock(pgm, 0x00) < 0) {
+      return -1;
+    }
+    if (updi_link_init(pgm) < 0) {
+      return -1;
+    }
+    return serialupdi_enter_progmode(pgm);
   }
 
   if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
@@ -335,6 +358,18 @@ def enter_progmode(self):
   }
 
   avrdude_message(MSG_DEBUG, "%s: Entered NVM programming mode\n", progname);
+
+  updi_sib_info * sib_info = updi_get_sib_info(pgm);
+
+  if (updi_read_sib(pgm, sib_info->sib_string, 32) < 0) {
+    avrdude_message(MSG_INFO, "%s: Read SIB operation failed\n", progname);
+    return -1;
+  }
+  if (serialupdi_decode_sib(pgm, sib_info) < 0) {
+    avrdude_message(MSG_INFO, "%s: Decode SIB_INFO failed\n", progname);
+    return -1;
+  }
+
   return 0;
 }
 
@@ -368,21 +403,11 @@ static int serialupdi_leave_progmode(PROGRAMMER * pgm)
 
 static int serialupdi_initialize(PROGRAMMER * pgm, AVRPART * p)
 {
-  updi_sib_info * sib_info = updi_get_sib_info(pgm);
-
   if (updi_link_init(pgm) < 0) {
     avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
     return -1;
   }
   avrdude_message(MSG_INFO, "%s: UPDI link initialization OK\n", progname);
-  if (updi_read_sib(pgm, sib_info->sib_string, 32) < 0) {
-    avrdude_message(MSG_INFO, "%s: Read SIB operation failed\n", progname);
-    return -1;
-  }
-  if (serialupdi_decode_sib(pgm, sib_info) < 0) {
-    avrdude_message(MSG_INFO, "%s: Decode SIB_INFO failed\n", progname);
-    return -1;
-  }
   if (serialupdi_enter_progmode(pgm) < 0) {
     avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
     return -1;
@@ -437,11 +462,19 @@ static int serialupdi_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
   if (strstr(mem->desc, "fuse") != 0) {
     return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
   }
+  if (strcmp(mem->desc, "lock") == 0) {
+    return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
+  }
   if (strcmp(mem->desc, "eeprom") == 0) {
     unsigned char buffer[1];
     buffer[0]=value;
     return updi_nvm_write_eeprom(pgm, p, mem->offset + addr, buffer, 1);
   }
+  if (strcmp(mem->desc, "flash") == 0) {
+    unsigned char buffer[1];
+    buffer[0]=value;
+    return updi_nvm_write_flash(pgm, p, mem->offset + addr, buffer, 1);
+  }
   return updi_write_byte(pgm, mem->offset + addr, value);
 }
 
@@ -478,12 +511,6 @@ static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
                                   unsigned int addr, unsigned int n_bytes)
 {
   int rc;
-
-  if (serialupdi_enter_progmode(pgm) < 0) {
-    avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
-    return -1;
-  }
-
   if (n_bytes > m->page_size) {
     unsigned int write_offset = addr;
     unsigned int remaining_bytes = n_bytes;
@@ -585,11 +612,6 @@ static int serialupdi_unlock(PROGRAMMER * pgm, AVRPART * p)
 
 static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
 {
-  if (serialupdi_enter_progmode(pgm) < 0) {
-    avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
-    return -1;
-  }
-
   if (updi_nvm_chip_erase(pgm, p) < 0) {
     avrdude_message(MSG_INFO, "%s: Chip erase failed, device might be locked, attempting unlock now\n", progname);
     return serialupdi_unlock(pgm, p);

From d1dddad8969ce56837d81baa8e950722fe14c3d0 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Sat, 18 Dec 2021 16:47:37 +0100
Subject: [PATCH 10/12] Added support for writing USERROW memory

---
 .gitignore       |   39 ++
 avrdude.conf.in  |    7 +
 serialupdi.c     |  965 +++++++++++++++++++++++++++++++++++
 serialupdi.h     |   43 ++
 updi_constants.h |  156 ++++++
 updi_link.c      |  930 +++++++++++++++++++++++++++++++++
 updi_link.h      |   59 +++
 updi_nvm.c       | 1275 ++++++++++++++++++++++++++++++++++++++++++++++
 updi_nvm.h       |   51 ++
 updi_readwrite.c |  316 ++++++++++++
 updi_readwrite.h |   51 ++
 updi_state.c     |   55 ++
 updi_state.h     |   85 ++++
 13 files changed, 4032 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 serialupdi.c
 create mode 100644 serialupdi.h
 create mode 100644 updi_constants.h
 create mode 100644 updi_link.c
 create mode 100644 updi_link.h
 create mode 100644 updi_nvm.c
 create mode 100644 updi_nvm.h
 create mode 100644 updi_readwrite.c
 create mode 100644 updi_readwrite.h
 create mode 100644 updi_state.c
 create mode 100644 updi_state.h

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..bb11dcce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,39 @@
+avrdude
+libavrdude.a
+libavrdude.la
+Makefile
+Makefile.in
+*.o
+*.lo
+libtool
+ltmain.sh
+.deps/
+.libs/
+INSTALL
+ac_cfg.h
+ac_cfg.h.in
+aclocal.m4
+autom4te.cache/
+avrdude.conf
+avrdude.conf.tmp
+avrdude.spec
+compile
+config.guess
+config.log
+config.status
+config.sub
+config_gram.c
+config_gram.h
+configure
+configure~
+depcomp
+doc/mdate-sh
+doc/texinfo.tex
+install-sh
+lexer.c
+missing
+stamp-h1
+windows/.deps/
+ylwrap
+m4/
+*.bin
diff --git a/avrdude.conf.in b/avrdude.conf.in
index 837c9f54..46b2da67 100644
--- a/avrdude.conf.in
+++ b/avrdude.conf.in
@@ -16562,6 +16562,13 @@ part
         readsize  = 0x4;
     ;
 
+    memory "userrow"
+        size      = 0x20;
+        offset    = 0x1080;
+        page_size = 0x20;
+        readsize  = 0x20;
+    ;
+
     memory "data"
         # SRAM, only used to supply the offset
         offset		= 0x1000000;
diff --git a/serialupdi.c b/serialupdi.c
new file mode 100644
index 00000000..839d7d81
--- /dev/null
+++ b/serialupdi.c
@@ -0,0 +1,965 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Interface to the SerialUPDI programmer.
+ *
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "serialupdi.h"
+#include "updi_link.h"
+#include "updi_state.h"
+#include "updi_readwrite.h"
+#include "updi_nvm.h"
+#include "updi_constants.h"
+
+static int serialupdi_enter_progmode(PROGRAMMER * pgm);
+static int serialupdi_leave_progmode(PROGRAMMER * pgm);
+
+static void serialupdi_setup(PROGRAMMER * pgm)
+{
+  if ((pgm->cookie = malloc(sizeof(updi_state))) == 0) {
+    avrdude_message(MSG_INFO,
+	    "%s: serialupdi_setup(): Out of memory allocating private data\n",
+	    progname);
+    exit(1);
+  }
+  memset(pgm->cookie, 0, sizeof(updi_state));
+  updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
+}
+
+static void serialupdi_teardown(PROGRAMMER * pgm)
+{
+  free(pgm->cookie);
+}
+
+static int serialupdi_open(PROGRAMMER * pgm, char * port)
+{
+  strcpy(pgm->port, port);
+  return updi_link_open(pgm);
+}
+
+typedef enum {
+  APPLY_RESET,
+  RELEASE_RESET
+} reset_mode;
+
+static int serialupdi_reset(PROGRAMMER * pgm, reset_mode mode)
+{
+/*
+    def reset(self, apply_reset):
+        """
+        Applies or releases an UPDI reset condition
+
+        :param apply_reset: True to apply, False to release
+        """
+        if apply_reset:
+            self.logger.info("Apply reset")
+            self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, constants.UPDI_RESET_REQ_VALUE)
+        else:
+            self.logger.info("Release reset")
+            self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, 0x00)
+*/
+  switch (mode) {
+    case APPLY_RESET:
+      avrdude_message(MSG_DEBUG, "%s: Sending reset request\n", progname);
+      return updi_write_cs(pgm, UPDI_ASI_RESET_REQ, UPDI_RESET_REQ_VALUE);
+    case RELEASE_RESET:
+      avrdude_message(MSG_DEBUG, "%s: Sending release reset request\n", progname);
+      return updi_write_cs(pgm, UPDI_ASI_RESET_REQ, 0x00);
+  }
+  return -1;
+}
+
+static int serialupdi_reset_connection(PROGRAMMER * pgm)
+{
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  return updi_link_init(pgm);
+}
+
+static int serialupdi_decode_sib(PROGRAMMER * pgm, updi_sib_info * sib_info)
+{
+  char * str_ptr;
+
+  sib_info->sib_string[SIB_INFO_STRING_LENGTH]=0;
+  avrdude_message(MSG_DEBUG, "%s: Received SIB: [%s]\n", progname, sib_info->sib_string);
+  memset(sib_info->family_string, 0, SIB_INFO_FAMILY_LENGTH+1);
+  memset(sib_info->nvm_string, 0, SIB_INFO_NVM_LENGTH+1);
+  memset(sib_info->debug_string, 0, SIB_INFO_DEBUG_LENGTH+1);
+  memset(sib_info->pdi_string, 0, SIB_INFO_PDI_LENGTH+1);
+  memset(sib_info->pdi_string, 0, SIB_INFO_PDI_LENGTH+1);
+  memset(sib_info->extra_string, 0, SIB_INFO_EXTRA_LENGTH+1);
+
+  memcpy(sib_info->family_string, sib_info->sib_string, SIB_INFO_FAMILY_LENGTH);
+  memcpy(sib_info->nvm_string, sib_info->sib_string + 8, SIB_INFO_NVM_LENGTH);
+  memcpy(sib_info->debug_string, sib_info->sib_string + 11, SIB_INFO_DEBUG_LENGTH);
+  memcpy(sib_info->pdi_string, sib_info->sib_string + 15, SIB_INFO_PDI_LENGTH);
+  strcpy(sib_info->extra_string, (char *)sib_info->sib_string + 19);
+
+  str_ptr = strstr(sib_info->nvm_string, ":");
+  if (!str_ptr) {
+    avrdude_message(MSG_INFO, "%s: Incorrect format of NVM string\n", progname);
+    return -1;
+  }
+  sib_info->nvm_version = *(str_ptr+1);
+
+  str_ptr = strstr(sib_info->debug_string, ":");
+  if (!str_ptr) {
+    avrdude_message(MSG_INFO, "%s: Incorrect format of DEBUG string\n", progname);
+    return -1;
+  }
+  sib_info->debug_version = *(str_ptr+1);
+
+  avrdude_message(MSG_DEBUG, "%s: Device family ID: %s\n", progname, sib_info->family_string);
+  avrdude_message(MSG_DEBUG, "%s: NVM interface: %s\n", progname, sib_info->nvm_string);
+  avrdude_message(MSG_DEBUG, "%s: Debug interface: %s\n", progname, sib_info->debug_string);
+  avrdude_message(MSG_DEBUG, "%s: PDI oscillator: %s\n", progname, sib_info->pdi_string);
+  avrdude_message(MSG_DEBUG, "%s: Extra information: %s\n", progname, sib_info->extra_string);
+  switch (sib_info->nvm_version) {
+    case '0':
+      avrdude_message(MSG_INFO, "%s: NVM type 0: 16-bit, page oriented write\n", progname);
+      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V0);
+      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
+      break;
+    case '2':
+      avrdude_message(MSG_INFO, "%s: NVM type 2: 24-bit, word oriented write\n", progname);
+      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V2);
+      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_24BIT);
+      break;
+    case '3':
+      avrdude_message(MSG_INFO, "%s: NVM type 3: 16-bit, page oriented\n", progname);
+      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V3);
+      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
+      break;
+    default:
+      avrdude_message(MSG_INFO, "%s: Unsupported NVM type: %c, please update software\n", progname, sib_info->nvm_version);
+      return -1;
+  }
+  return 0;
+}
+
+static void serialupdi_close(PROGRAMMER * pgm)
+{
+  avrdude_message(MSG_INFO, "%s: Leaving NVM programming mode\n", progname);
+
+  if (serialupdi_leave_progmode(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: Unable to leave NVM programming mode\n", progname);
+  }
+  updi_link_close(pgm);
+}
+
+static int serialupdi_wait_for_unlock(PROGRAMMER * pgm, unsigned int ms) {
+/*
+    def wait_unlocked(self, timeout_ms):
+        """
+        Waits for the device to be unlocked.
+        All devices boot up as locked until proven otherwise
+
+        :param timeout_ms: number of milliseconds to wait
+        """
+        timeout = Timeout(timeout_ms)
+
+        while not timeout.expired():
+            if not self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & (
+                    1 << constants.UPDI_ASI_SYS_STATUS_LOCKSTATUS):
+                return True
+
+        self.logger.error("Timeout waiting for device to unlock")
+        return False
+*/  
+  unsigned long start_time;
+  unsigned long current_time;
+  struct timeval tv;
+  uint8_t status;
+  gettimeofday (&tv, NULL);
+  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  do {
+    if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &status) >= 0) {
+      if (!(status & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS))) {
+        return 0;
+      }
+    }
+    gettimeofday (&tv, NULL);
+    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  } while ((current_time - start_time) < (ms * 1000));
+
+  avrdude_message(MSG_INFO, "%s: Timeout waiting for device to unlock\n", progname);
+  return -1;
+}
+
+typedef enum {
+  WAIT_FOR_UROW_LOW,
+  WAIT_FOR_UROW_HIGH
+} urow_wait_mode;
+
+static int serialupdi_wait_for_urow(PROGRAMMER * pgm, unsigned int ms, urow_wait_mode mode) {
+/*
+    def wait_urow_prog(self, timeout_ms, wait_for_high):
+        """
+        Waits for the device to be in user row write mode
+        User row is writeable on a locked device using this mechanism
+
+        :param timeout_ms: number of milliseconds to wait
+        :param wait_for_high: set True to wait for bit to go high; False to wait for low
+        """
+        timeout = Timeout(timeout_ms)
+
+        while not timeout.expired():
+            status = self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS)
+            if wait_for_high:
+                if status & (1 << constants.UPDI_ASI_SYS_STATUS_UROWPROG):
+                    return True
+            else:
+                if not status & (1 << constants.UPDI_ASI_SYS_STATUS_UROWPROG):
+                    return True
+
+        self.logger.error("Timeout waiting for device to enter UROW WRITE mode")
+        return False
+*/  
+  unsigned long start_time;
+  unsigned long current_time;
+  struct timeval tv;
+  uint8_t status;
+  gettimeofday (&tv, NULL);
+  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  do {
+    if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &status) >= 0) {
+      if (mode == WAIT_FOR_UROW_HIGH) {
+        if (status & (1 << UPDI_ASI_SYS_STATUS_UROWPROG)) {
+          return 0;
+        }
+      } else {
+        if (!(status & (1 << UPDI_ASI_SYS_STATUS_UROWPROG))) {
+          return 0;
+        }
+      }
+    }
+    gettimeofday (&tv, NULL);
+    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  } while ((current_time - start_time) < (ms * 1000));
+
+  avrdude_message(MSG_INFO, "%s: Timeout waiting for device to complete UROW WRITE\n", progname);
+  return -1;
+}
+
+static int serialupdi_in_prog_mode(PROGRAMMER * pgm, uint8_t * in_prog_mode)
+{
+/*
+    def in_prog_mode(self):
+        """
+        Checks whether the NVM PROG flag is up
+        """
+        if self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & (1 << constants.UPDI_ASI_SYS_STATUS_NVMPROG):
+            return True
+        return False
+*/
+  uint8_t value;
+  int rc;
+  
+  rc = updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value);
+  
+  if (rc < 0) {
+    avrdude_message(MSG_INFO, "%s: Read CS operation failed\n", progname);
+    return rc;
+  }
+
+  if (value & (1 << UPDI_ASI_SYS_STATUS_NVMPROG)) {
+    *in_prog_mode = 1;
+  } else {
+    *in_prog_mode = 0;
+  }
+  return 0;
+}
+
+static int serialupdi_enter_progmode(PROGRAMMER * pgm)
+{
+/*
+def enter_progmode(self):
+        """
+        Enters into NVM programming mode
+        """
+        # First check if NVM is already enabled
+        if self.in_prog_mode():
+            self.logger.info("Already in NVM programming mode")
+            return True
+
+        self.logger.info("Entering NVM programming mode")
+
+        # Put in the key
+        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_NVM)
+
+        # Check key status
+        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
+        self.logger.debug("Key status = 0x%02X", key_status)
+
+        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_NVMPROG):
+            self.logger.error("Key status = 0x%02X", key_status)
+            raise IOError("Key not accepted")
+
+        # Toggle reset
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+
+        # And wait for unlock
+        if not self.wait_unlocked(100):
+            raise IOError("Failed to enter NVM programming mode: device is locked")
+
+        # Check for NVMPROG flag
+        if not self.in_prog_mode():
+            raise IOError("Failed to enter NVM programming mode")
+
+        self.logger.debug("Now in NVM programming mode")
+        return True
+*/
+  uint8_t in_prog_mode;
+  unsigned char buffer[8];
+  uint8_t key_status;
+
+  if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed\n", progname);
+    return -1;
+  }
+  if (in_prog_mode) {
+    avrdude_message(MSG_DEBUG, "%s: Already in prog mode\n", progname);
+    return 0;
+  }
+
+  memcpy(buffer, UPDI_KEY_NVM, sizeof(buffer));
+  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Writing NVM KEY failed\n", progname);
+    return -1;
+  }
+
+  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
+
+  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_NVMPROG))) {
+    avrdude_message(MSG_INFO, "%s: Key was not accepted\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_wait_for_unlock(pgm, 100) < 0) {
+    avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode: device is locked\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed\n", progname);
+    return -1;
+  }
+
+  if (!in_prog_mode) {
+    avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode\n", progname);
+    return -1;
+  }
+
+  avrdude_message(MSG_DEBUG, "%s: Entered NVM programming mode\n", progname);
+
+  return 0;
+}
+
+static int serialupdi_leave_progmode(PROGRAMMER * pgm)
+{
+/*
+    def leave_progmode(self):
+        """
+        Disables UPDI which releases any keys enabled
+        """
+        self.logger.info("Leaving NVM programming mode")
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+        self.readwrite.write_cs(constants.UPDI_CS_CTRLB,
+                                (1 << constants.UPDI_CTRLB_UPDIDIS_BIT) | (1 << constants.UPDI_CTRLB_CCDETDIS_BIT))
+*/
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  return updi_write_cs(pgm, UPDI_CS_CTRLB, (1 << UPDI_CTRLB_UPDIDIS_BIT) | (1 << UPDI_CTRLB_CCDETDIS_BIT));
+}
+
+static int serialupdi_write_userrow(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                    unsigned int page_size,
+                                    unsigned int addr, unsigned int n_bytes)
+{
+  /*
+    def write_user_row_locked_device(self, address, data):
+        """
+        Writes data to the user row when the device is locked, using a key.
+        """
+        # Put in the key
+        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_UROW)
+
+        # Check key status
+        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
+        self.logger.debug("Key status = 0x%02X", key_status)
+
+        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_UROWWRITE):
+            raise PymcuprogError("Key not accepted")
+
+        # Toggle reset
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+
+        # Wait for mode to be entered
+        if not self.wait_urow_prog(500, wait_for_high=True):
+            raise PymcuprogError("Failed to enter urow write mode using key")
+
+        # At this point we can write one 'page' to the device, and have it transfered into the user row
+        # Transfer data
+        self.readwrite.write_data(address, data)
+
+        # Finalize
+        self.readwrite.write_cs(constants.UPDI_ASI_SYS_CTRLA,
+                                (1 << constants.UPDI_ASI_SYS_CTRLA_UROW_FINAL) |
+                                (1 << constants.UPDI_CTRLB_CCDETDIS_BIT))
+
+        # Wait for mode to be exited
+        if not self.wait_urow_prog(500, wait_for_high=False):
+            # Toggle reset
+            self.reset(apply_reset=True)
+            self.reset(apply_reset=False)
+            raise PymcuprogError("Failed to exit urow write mode")
+
+        # Clear status
+        self.readwrite.write_cs(constants.UPDI_ASI_KEY_STATUS,
+                                (1 << constants.UPDI_ASI_KEY_STATUS_UROWWRITE) |
+                                (1 << constants.UPDI_CTRLB_CCDETDIS_BIT))
+
+        # Toggle reset
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+  */
+  unsigned char buffer[8];
+  uint8_t key_status;
+
+  memcpy(buffer, UPDI_KEY_UROW, sizeof(buffer));
+  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Writing USERROW KEY failed\n", progname);
+    return -1;
+  }
+
+  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
+
+  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_UROWWRITE))) {
+    avrdude_message(MSG_INFO, "%s: Key was not accepted\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_wait_for_urow(pgm, 500, WAIT_FOR_UROW_HIGH) < 0) {
+    avrdude_message(MSG_INFO, "%s: Failed to enter USERROW programming mode\n", progname);
+    return -1;
+  }
+
+  if (updi_write_data(pgm, m->offset+addr, m->buf + addr, n_bytes) < 0) {
+    avrdude_message(MSG_INFO, "%s: Writing USER ROW failed\n", progname);
+    return -1;
+  }
+
+  if (updi_write_cs(pgm, UPDI_ASI_SYS_CTRLA, (1 << UPDI_ASI_SYS_CTRLA_UROW_FINAL) |
+                                             (1 << UPDI_CTRLB_CCDETDIS_BIT)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Failed trying to commit user row write\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_wait_for_urow(pgm, 500, WAIT_FOR_UROW_LOW) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Failed to exit USERROW programming mode\n", progname);
+
+    if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+      avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+      return -1;
+    }
+
+    if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+      avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+      return -1;
+    }
+  }
+
+  if (updi_write_cs(pgm, UPDI_ASI_KEY_STATUS, (1 << UPDI_ASI_KEY_STATUS_UROWWRITE) |
+                                              (1 << UPDI_CTRLB_CCDETDIS_BIT)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Failed trying to complete user row write\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  serialupdi_reset_connection(pgm);
+
+  serialupdi_enter_progmode(pgm);
+
+  return 0;
+}
+
+static int serialupdi_initialize(PROGRAMMER * pgm, AVRPART * p)
+{
+  uint8_t value;
+  uint8_t reset_link_required=0;
+  
+  if (updi_link_init(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_INFO, "%s: UPDI link initialization OK\n", progname);
+
+  if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value)<0) {
+
+    /* let's try reset the connection */
+    if (!serialupdi_reset_connection(pgm)) {
+      return -1;
+    }
+
+    if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value)<0) {
+      avrdude_message(MSG_INFO, "%s: Read CS operation during initialization failed\n", progname);
+      return -1;
+    }
+  }
+  if (value & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS)) {
+    avrdude_message(MSG_INFO, "%s: Device is locked\n", progname);
+  }
+  if (value & (1 << UPDI_ASI_SYS_STATUS_UROWPROG)) {
+    avrdude_message(MSG_INFO, "%s: Device in USER ROW programming state, leaving programming mode\n", progname);
+    reset_link_required = 1;
+  }
+  if (value & (1 << UPDI_ASI_SYS_STATUS_NVMPROG)) {
+    avrdude_message(MSG_INFO, "%s: Device in NVM programming state, leaving programming mode\n", progname);
+    reset_link_required = 1;
+  }
+  if (value & (1 << UPDI_ASI_SYS_STATUS_INSLEEP)) {
+    avrdude_message(MSG_INFO, "%s: Device is in SLEEP mode\n", progname);
+  }
+  if (value & (1 << UPDI_ASI_SYS_STATUS_RSTSYS)) {
+    avrdude_message(MSG_INFO, "%s: Device in reset status, trying to release it\n", progname);
+    if (serialupdi_reset(pgm, RELEASE_RESET)<0) {
+      return -1;
+    }
+  }
+  if (reset_link_required) {
+    if (serialupdi_reset_connection(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: UPDI link reset failed\n", progname);
+      return -1;
+    }
+  }
+
+  updi_sib_info * sib_info = updi_get_sib_info(pgm);
+
+  if (updi_read_sib(pgm, sib_info->sib_string, 32) < 0) {
+    /* this should never happen, let's try to reset connection and try again */
+    if (serialupdi_reset_connection(pgm) < 0) {
+      avrdude_message(MSG_INFO, "%s: SerialUPDI reset connection failed\n", progname);  
+      return -1;
+    }
+    if (updi_read_sib(pgm, sib_info->sib_string, 32) < 0) {
+      avrdude_message(MSG_INFO, "%s: Read SIB operation failed\n", progname);  
+      return -1;
+    }
+  }
+  if (serialupdi_decode_sib(pgm, sib_info) < 0) {
+    avrdude_message(MSG_INFO, "%s: Decode SIB_INFO failed\n", progname);
+    return -1;
+  }
+
+  if (updi_link_init(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
+    return -1;
+  }
+
+  avrdude_message(MSG_INFO, "%s: Entering NVM programming mode\n", progname);
+    /* try, but ignore failure */
+  serialupdi_enter_progmode(pgm);
+
+  return 0;
+}
+
+static void serialupdi_disable(PROGRAMMER * pgm)
+{
+  /* Do nothing. */
+
+  return;
+}
+
+static void serialupdi_enable(PROGRAMMER * pgm)
+{
+  /* Do nothing. */
+
+  return;
+}
+
+static void serialupdi_display(PROGRAMMER * pgm, const char * p)
+{
+  return;
+}
+
+static int serialupdi_cmd(PROGRAMMER * pgm, const unsigned char * cmd,
+                          unsigned char * res)
+{
+  avrdude_message(MSG_INFO, "%s: error: cmd %s[%s] not implemented yet\n",
+    	    progname, cmd, res);
+  return -1;
+}
+
+static int serialupdi_program_enable(PROGRAMMER * pgm, AVRPART * p)
+{
+  avrdude_message(MSG_INFO, "%s: error: program enable not implemented yet\n",
+    	    progname);
+  return -1;
+}
+
+static int serialupdi_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, 
+                                unsigned long addr, unsigned char * value)
+{
+  return updi_read_byte(pgm, mem->offset + addr, value);
+}
+
+static int serialupdi_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
+                                 unsigned long addr, unsigned char value)
+{
+  if (strstr(mem->desc, "fuse") != 0) {
+    return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
+  }
+  if (strcmp(mem->desc, "lock") == 0) {
+    return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
+  }
+  if (strcmp(mem->desc, "eeprom") == 0) {
+    unsigned char buffer[1];
+    buffer[0]=value;
+    return updi_nvm_write_eeprom(pgm, p, mem->offset + addr, buffer, 1);
+  }
+  if (strcmp(mem->desc, "flash") == 0) {
+    unsigned char buffer[1];
+    buffer[0]=value;
+    return updi_nvm_write_flash(pgm, p, mem->offset + addr, buffer, 1);
+  }
+  return updi_write_byte(pgm, mem->offset + addr, value);
+}
+
+
+static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                 unsigned int page_size,
+                                 unsigned int addr, unsigned int n_bytes)
+{
+  if (n_bytes > m->readsize) {
+    unsigned int read_offset = addr;
+    unsigned int remaining_bytes = n_bytes;
+    int read_bytes = 0;
+    int rc;
+    while (remaining_bytes > 0) {
+      rc = updi_read_data(pgm, m->offset + read_offset, m->buf + read_offset, 
+                          remaining_bytes > m->readsize ? m->readsize : remaining_bytes);
+      if (rc < 0) {
+        avrdude_message(MSG_INFO, "%s: Paged load operation failed\n", progname);
+        return rc;
+      } else {
+        read_bytes+=rc;
+        read_offset+=m->readsize;
+        remaining_bytes-=m->readsize;
+      }
+    }
+    return read_bytes;
+  } else {
+    return updi_read_data(pgm, m->offset + addr, m->buf + addr, n_bytes);
+  }
+}
+
+static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                  unsigned int page_size,
+                                  unsigned int addr, unsigned int n_bytes)
+{
+  int rc;
+  if (n_bytes > m->page_size) {
+    unsigned int write_offset = addr;
+    unsigned int remaining_bytes = n_bytes;
+    int write_bytes = 0;
+    while (remaining_bytes > 0) {
+
+      if (strcmp(m->desc, "eeprom")==0) {
+        rc = updi_nvm_write_eeprom(pgm, p, m->offset + write_offset, m->buf + write_offset, 
+                                   remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
+      } else if (strcmp(m->desc, "flash")==0) {
+        rc = updi_nvm_write_flash(pgm, p, m->offset + write_offset, m->buf + write_offset, 
+                                  remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
+      } else if (strcmp(m->desc, "userrow")==0) {
+        rc = serialupdi_write_userrow(pgm, p, m, page_size, write_offset, 
+                                      remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
+      } else if (strcmp(m->desc, "fuses")==0) {
+        avrdude_message(MSG_DEBUG, "%s: Page write operation requested for fuses, falling back to byte-level write\n", progname);
+        return -1;
+      } else {
+        avrdude_message(MSG_INFO, "%s: Invalid memory type: <%s:%d>, 0x%06X, %d (0x%04X)\n", progname, m->desc, page_size, addr, n_bytes, n_bytes);
+        rc = -1;
+      }
+
+      if (rc < 0) {
+        avrdude_message(MSG_INFO, "%s: Paged write operation failed\n", progname);
+        return rc;
+      } else {
+        write_bytes+=rc;
+        write_offset+=m->page_size;
+        remaining_bytes-=m->page_size;
+      }
+    }
+    return write_bytes;
+  } else {
+    if (strcmp(m->desc, "eeprom")==0) {
+      rc = updi_nvm_write_eeprom(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
+    } else if (strcmp(m->desc, "flash")==0) {
+      rc = updi_nvm_write_flash(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
+    } else if (strcmp(m->desc, "userrow")==0) {
+      rc = serialupdi_write_userrow(pgm, p, m, page_size, addr, n_bytes);
+    } else if (strcmp(m->desc, "fuses")==0) {
+        avrdude_message(MSG_DEBUG, "%s: Page write operation requested for fuses, falling back to byte-level write\n", progname);
+        rc = -1;
+    } else {
+      avrdude_message(MSG_INFO, "%s: Invalid memory type: <%s:%d>, 0x%06X, %d (0x%04X)\n", progname, m->desc, page_size, addr, n_bytes, n_bytes);
+      rc = -1;
+    }
+    return rc;
+  }
+}
+
+static int serialupdi_unlock(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def unlock(self):
+        """
+        Unlock by chip erase
+        """
+        # Put in the key
+        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE)
+
+        # Check key status
+        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
+        self.logger.debug("Key status = 0x%02X", key_status)
+
+        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE):
+            raise PymcuprogError("Key not accepted")
+
+        # Toggle reset
+        self.reset(apply_reset=True)
+        self.reset(apply_reset=False)
+
+        # And wait for unlock
+        if not self.wait_unlocked(500):
+            raise PymcuprogError("Failed to chip erase using key")
+*/
+  unsigned char buffer[8];
+  uint8_t key_status;
+  
+  memcpy(buffer, UPDI_KEY_CHIPERASE, sizeof(buffer));
+
+  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
+    avrdude_message(MSG_INFO, "%s: Writing NVM KEY failed\n", progname);
+    return -1;
+  }
+
+  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
+    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
+
+  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_CHIPERASE))) {
+    avrdude_message(MSG_INFO, "%s: Key not accepted\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
+    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
+    return -1;
+  }
+
+  if (serialupdi_wait_for_unlock(pgm, 500) < 0) {
+    avrdude_message(MSG_INFO, "%s: Waiting for unlock failed\n", progname);
+    return -1;
+  }
+
+  if (updi_link_init(pgm) < 0) {
+    avrdude_message(MSG_INFO, "%s: UPDI link reinitialization failed\n", progname);
+    return -1;
+  }
+
+  return serialupdi_enter_progmode(pgm);
+}
+
+static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
+{
+  uint8_t value;
+
+  if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value)<0) {
+    avrdude_message(MSG_INFO, "%s: Read CS operation during chip erase failed\n", progname);
+    return -1;
+  }
+  
+  if (value & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS)) {
+    avrdude_message(MSG_INFO, "%s: Device is locked\n", progname);
+    if (ovsigck) {
+      avrdude_message(MSG_INFO, "%s: Attempting device erase\n", progname);
+      return serialupdi_unlock(pgm, p);
+    }
+  } else {
+    return updi_nvm_chip_erase(pgm, p);
+  }
+  return -1;
+}
+
+static int serialupdi_page_erase(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
+                                 unsigned int baseaddr)
+{
+  avrdude_message(MSG_INFO, "%s: error: page erase not implemented yet\n",
+    	    progname);
+  return -1;
+}
+
+static int serialupdi_read_signature(PROGRAMMER * pgm, AVRPART *p, AVRMEM *m) {
+
+  uint8_t value;
+
+  if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value)<0) {
+    avrdude_message(MSG_INFO, "%s: Read CS operation during signature read failed\n", progname);
+    return -1;
+  }
+
+  if (value & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS)) {
+    m->buf[0]=0x00;
+    m->buf[1]=0x00;
+    m->buf[2]=0x00;
+  } else {
+    updi_read_byte(pgm, m->offset + 0, m->buf);
+    updi_read_byte(pgm, m->offset + 1, m->buf+1);
+    updi_read_byte(pgm, m->offset + 2, m->buf+2);
+  }
+
+  return 3;
+}
+
+static int serialupdi_read_sib(PROGRAMMER * pgm, AVRPART *p, char *sib) {
+
+  updi_sib_info * sib_info = updi_get_sib_info(pgm);
+
+  memcpy(sib, sib_info->sib_string, 32);
+  
+  return 0;
+}
+
+
+void serialupdi_initpgm(PROGRAMMER * pgm)
+{
+  strcpy(pgm->type, "serialupdi");
+
+  /*
+   * mandatory functions
+   */
+
+  pgm->initialize     = serialupdi_initialize;
+  pgm->display        = serialupdi_display;
+  pgm->enable         = serialupdi_enable;
+  pgm->disable        = serialupdi_disable;
+  pgm->program_enable = serialupdi_program_enable;
+  pgm->chip_erase     = serialupdi_chip_erase;
+  pgm->cmd            = serialupdi_cmd;
+  pgm->open           = serialupdi_open;
+  pgm->close          = serialupdi_close;
+  pgm->read_byte      = serialupdi_read_byte;
+  pgm->write_byte     = serialupdi_write_byte;
+
+  /*
+   * optional functions
+   */
+
+  pgm->unlock         = serialupdi_unlock;
+  pgm->paged_write    = serialupdi_paged_write;
+  pgm->read_sig_bytes = serialupdi_read_signature;
+  pgm->read_sib       = serialupdi_read_sib;
+  pgm->paged_load     = serialupdi_paged_load;
+  pgm->page_erase     = serialupdi_page_erase;
+  pgm->setup          = serialupdi_setup;
+  pgm->teardown       = serialupdi_teardown;
+
+}
+
+const char serialupdi_desc[] = "Driver for SerialUPDI programmers";
diff --git a/serialupdi.h b/serialupdi.h
new file mode 100644
index 00000000..ff7270d5
--- /dev/null
+++ b/serialupdi.h
@@ -0,0 +1,43 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef serialupdi_h
+#define serialupdi_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char serialupdi_desc[];
+void serialupdi_initpgm (PROGRAMMER * pgm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* serialupdi_h */
diff --git a/updi_constants.h b/updi_constants.h
new file mode 100644
index 00000000..ff8a446f
--- /dev/null
+++ b/updi_constants.h
@@ -0,0 +1,156 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_constants_h
+#define updi_constants_h
+
+#define UPDI_BREAK       0x00
+
+#define UPDI_LDS         0x00
+#define UPDI_STS         0x40
+#define UPDI_LD          0x20
+#define UPDI_ST          0x60
+#define UPDI_LDCS        0x80
+#define UPDI_STCS        0xC0
+#define UPDI_REPEAT      0xA0
+#define UPDI_KEY         0xE0
+
+#define UPDI_PTR         0x00
+#define UPDI_PTR_INC     0x04
+#define UPDI_PTR_ADDRESS 0x08
+
+#define UPDI_ADDRESS_8   0x00
+#define UPDI_ADDRESS_16  0x04
+#define UPDI_ADDRESS_24  0x08
+
+#define UPDI_DATA_8      0x00
+#define UPDI_DATA_16     0x01
+#define UPDI_DATA_24     0x02
+
+#define UPDI_KEY_SIB     0x04
+#define UPDI_KEY_KEY     0x00
+
+#define UPDI_KEY_64      0x00
+#define UPDI_KEY_128     0x01
+#define UPDI_KEY_256     0x02
+
+#define UPDI_SIB_8BYTES  UPDI_KEY_64
+#define UPDI_SIB_16BYTES UPDI_KEY_128
+#define UPDI_SIB_32BYTES UPDI_KEY_256
+
+#define UPDI_REPEAT_BYTE 0x00
+#define UPDI_REPEAT_WORD 0x01
+
+#define UPDI_PHY_SYNC    0x55
+#define UPDI_PHY_ACK     0x40
+
+#define UPDI_MAX_REPEAT_SIZE (0xFF+1) // Repeat counter of 1-byte, with off-by-one counting
+
+//# CS and ASI Register Address map
+#define UPDI_CS_STATUSA     0x00
+#define UPDI_CS_STATUSB     0x01
+#define UPDI_CS_CTRLA       0x02
+#define UPDI_CS_CTRLB       0x03
+#define UPDI_ASI_KEY_STATUS 0x07
+#define UPDI_ASI_RESET_REQ  0x08
+#define UPDI_ASI_CTRLA      0x09
+#define UPDI_ASI_SYS_CTRLA  0x0A
+#define UPDI_ASI_SYS_STATUS 0x0B
+#define UPDI_ASI_CRC_STATUS 0x0C
+
+#define UPDI_CTRLA_IBDLY_BIT    7
+#define UPDI_CTRLB_CCDETDIS_BIT 3
+#define UPDI_CTRLB_UPDIDIS_BIT  2
+
+#define UPDI_KEY_NVM       "NVMProg "
+#define UPDI_KEY_CHIPERASE "NVMErase"
+#define UPDI_KEY_UROW      "NVMUs&te"
+
+#define UPDI_ASI_STATUSA_REVID 4
+#define UPDI_ASI_STATUSB_PESIG 0
+
+#define UPDI_ASI_KEY_STATUS_CHIPERASE  3
+#define UPDI_ASI_KEY_STATUS_NVMPROG    4
+#define UPDI_ASI_KEY_STATUS_UROWWRITE  5
+
+#define UPDI_ASI_SYS_STATUS_RSTSYS     5
+#define UPDI_ASI_SYS_STATUS_INSLEEP    4
+#define UPDI_ASI_SYS_STATUS_NVMPROG    3
+#define UPDI_ASI_SYS_STATUS_UROWPROG   2
+#define UPDI_ASI_SYS_STATUS_LOCKSTATUS 0
+
+#define UPDI_ASI_SYS_CTRLA_UROW_FINAL  1
+
+#define UPDI_RESET_REQ_VALUE  0x59
+
+// FLASH CONTROLLER
+#define UPDI_NVMCTRL_CTRLA    0x00
+#define UPDI_NVMCTRL_CTRLB    0x01
+#define UPDI_NVMCTRL_STATUS   0x02
+#define UPDI_NVMCTRL_INTCTRL  0x03
+#define UPDI_NVMCTRL_INTFLAGS 0x04
+#define UPDI_NVMCTRL_DATAL    0x06
+#define UPDI_NVMCTRL_DATAH    0x07
+#define UPDI_NVMCTRL_ADDRL    0x08
+#define UPDI_NVMCTRL_ADDRH    0x09
+
+// NVMCTRL v0 CTRLA
+#define UPDI_V0_NVMCTRL_CTRLA_NOP              0x00
+#define UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE       0x01
+#define UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE       0x02
+#define UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE 0x03
+#define UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR  0x04
+#define UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE       0x05
+#define UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM     0x06
+#define UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE       0x07
+
+// NVMCTRL v2 CTRLA
+#define UPDI_V2_NVMCTRL_CTRLA_NOCMD              0x00
+#define UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE        0x02
+#define UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE   0x08
+#define UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE 0x13
+#define UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE         0x20
+#define UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE       0x30
+
+// NVMCTRL v3 CTRLA
+#define UPDI_V3_NVMCTRL_CTRLA_NOCMD                    0x00
+#define UPDI_V3_NVMCTRL_CTRLA_NOP                      0x01
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE         0x04
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE_WRITE   0x05
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE         0x08
+#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR  0x0F
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_WRITE        0x14
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE  0x15
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE        0x17
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_BUFFER_CLEAR 0x1F
+#define UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE               0x20
+#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE             0x30
+
+#define UPDI_NVM_STATUS_WRITE_ERROR 2
+#define UPDI_NVM_STATUS_EEPROM_BUSY 1
+#define UPDI_NVM_STATUS_FLASH_BUSY  0
+
+#endif /* updi_constants_h */
diff --git a/updi_link.c b/updi_link.c
new file mode 100644
index 00000000..b1af2b87
--- /dev/null
+++ b/updi_link.c
@@ -0,0 +1,930 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "updi_link.h"
+#include "updi_constants.h"
+#include "updi_state.h"
+
+#include <sys/time.h>
+
+void msleep(int tms)
+{
+    struct timeval tv;
+    tv.tv_sec  = tms / 1000;
+    tv.tv_usec = (tms % 1000) * 1000;
+    select (0, NULL, NULL, NULL, &tv);
+}
+
+static int updi_physical_open(PROGRAMMER* pgm, int baudrate, unsigned long cflags)
+{
+  serial_recv_timeout = 100;
+  union pinfo pinfo;
+
+  pinfo.serialinfo.baud = baudrate;
+  pinfo.serialinfo.cflags = cflags;
+
+  avrdude_message(MSG_DEBUG, "%s: Opening serial port...\n", progname);
+
+  if (serial_open(pgm->port, pinfo, &pgm->fd)==-1) {
+
+    avrdude_message(MSG_DEBUG, "%s: Serial port open failed!\n", progname);
+    return -1;
+  }
+
+  /*
+   * drain any extraneous input
+   */
+  serial_drain(&pgm->fd, 0);
+
+  return 0;
+}
+
+static void updi_physical_close(PROGRAMMER* pgm)
+{
+  serial_close(&pgm->fd);
+  pgm->fd.ifd = -1;
+}
+
+static int updi_physical_send(PROGRAMMER * pgm, unsigned char * buf, size_t len)
+{
+  size_t i;
+  int rv;
+
+  avrdude_message(MSG_DEBUG, "%s: Sending %lu bytes [", progname, len);
+  for (i=0; i<len; i++) {
+    avrdude_message(MSG_DEBUG, "0x%02x", buf[i]);
+    if (i<len-1) {
+      avrdude_message(MSG_DEBUG, ", ");
+    }
+  }
+  avrdude_message(MSG_DEBUG, "]\n");
+
+  rv = serial_send(&pgm->fd, buf, len);
+  serial_recv(&pgm->fd, buf, len);
+  return rv;
+}
+
+static int updi_physical_recv(PROGRAMMER * pgm, unsigned char * buf, size_t len)
+{
+  size_t i;
+  int rv;
+
+  rv = serial_recv(&pgm->fd, buf, len);
+  if (rv < 0) {
+    avrdude_message(MSG_DEBUG,
+      "%s: serialupdi_recv(): programmer is not responding\n",
+      progname);
+    return -1;
+  }
+
+  avrdude_message(MSG_DEBUG, "%s: Received %lu bytes [", progname, len);
+  for (i=0; i<len; i++) {
+    avrdude_message(MSG_DEBUG, "0x%02x", buf[i]);
+    if (i<len-1) {
+      avrdude_message(MSG_DEBUG, ", ");
+    }
+  }
+  avrdude_message(MSG_DEBUG, "]\n");
+
+  return len;
+}
+
+static int updi_physical_send_double_break(PROGRAMMER * pgm)
+{
+  unsigned char buffer[1];
+
+  avrdude_message(MSG_DEBUG, "%s: Sending double break\n", progname);
+
+  updi_physical_close(pgm);
+
+  if (updi_physical_open(pgm, 300, SERIAL_8E1)==-1) {
+
+    return -1;
+  }
+
+  buffer[0] = UPDI_BREAK;
+
+  serial_send(&pgm->fd, buffer, 1);
+  serial_recv(&pgm->fd, buffer, 1);
+
+  msleep(100);
+
+  buffer[0] = UPDI_BREAK;
+
+  serial_send(&pgm->fd, buffer, 1);
+  serial_recv(&pgm->fd, buffer, 1);
+
+  updi_physical_close(pgm);
+
+  return updi_physical_open(pgm, pgm->baudrate? pgm->baudrate: 115200, SERIAL_8E2);
+}
+
+int updi_physical_sib(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size)
+{
+/*
+    def sib(self):
+        """
+        System information block is just a string coming back from a SIB command
+        """
+        self.send([
+            constants.UPDI_PHY_SYNC,
+            constants.UPDI_KEY | constants.UPDI_KEY_SIB | constants.UPDI_SIB_32BYTES])
+        return self.ser.readline()
+*/
+  unsigned char send_buffer[2];
+
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_KEY | UPDI_KEY_SIB | UPDI_SIB_32BYTES;
+
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: SIB request send failed\n", progname);
+    return -1;
+  }
+
+  return updi_physical_recv(pgm, buffer, size);
+}
+
+int updi_link_open(PROGRAMMER * pgm) 
+{
+  if (updi_physical_open(pgm, pgm->baudrate? pgm->baudrate: 115200, SERIAL_8E2) < 0) {
+    return -1;
+  }
+  return updi_physical_send_double_break(pgm);
+}
+
+void updi_link_close(PROGRAMMER * pgm)
+{
+  updi_physical_close(pgm);
+}
+
+static int updi_link_init_session_parameters(PROGRAMMER * pgm) 
+{
+/*
+    def _init_session_parameters(self):
+        """
+        Set the inter-byte delay bit and disable collision detection
+        """
+        self.stcs(constants.UPDI_CS_CTRLB, 1 << constants.UPDI_CTRLB_CCDETDIS_BIT)
+        self.stcs(constants.UPDI_CS_CTRLA, 1 << constants.UPDI_CTRLA_IBDLY_BIT)
+*/
+  if (updi_link_stcs(pgm, UPDI_CS_CTRLB, 1 << UPDI_CTRLB_CCDETDIS_BIT) < 0) {
+    return -1;
+  }
+
+  if (updi_link_stcs(pgm, UPDI_CS_CTRLA, 1 << UPDI_CTRLA_IBDLY_BIT) < 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int updi_link_check(PROGRAMMER * pgm)
+{
+/*
+    def _check_datalink(self):
+        """
+        Check UPDI by loading CS STATUSA
+        """
+        try:
+            if self.ldcs(constants.UPDI_CS_STATUSA) != 0:
+                self.logger.info("UPDI init OK")
+                return True
+        except PymcuprogError:
+            self.logger.warning("Check failed")
+            return False
+        self.logger.info("UPDI not OK - reinitialisation required")
+        return False
+*/
+  int result;
+  uint8_t value;
+  result = updi_link_ldcs(pgm, UPDI_CS_STATUSA, &value);
+  if (result < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Check failed\n", progname);
+    return -1;
+  } else {
+    if (value > 0) {
+      avrdude_message(MSG_DEBUG, "%s: UDPI init OK\n", progname);
+      return 0;
+    } else {
+      avrdude_message(MSG_DEBUG, "%s: UDPI not OK - reinitialisation required\n", progname);
+      return -1;
+    }
+  }
+}
+
+
+int updi_link_init(PROGRAMMER * pgm)
+{
+/*
+    def init_datalink(self):
+        """
+        Init DL layer
+        """
+        self._init_session_parameters()
+        # Check
+        if not self._check_datalink():
+            # Send double break if all is not well, and re-check
+            self.updi_phy.send_double_break()
+            self._init_session_parameters()
+            if not self._check_datalink():
+                raise PymcuprogError("UPDI initialisation failed")
+*/
+  if (updi_link_init_session_parameters(pgm) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Session initialisation failed\n", progname);
+    return -1;
+  }
+
+  if (updi_link_check(pgm) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Datalink not active, resetting...\n", progname);
+    if (updi_physical_send_double_break(pgm) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Datalink initialisation failed\n", progname);
+      return -1;
+    }
+    if (updi_link_init_session_parameters(pgm) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Session initialisation failed\n", progname);
+      return -1;
+    }
+    if (updi_link_check(pgm) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Restoring datalink failed\n", progname);
+      return -1;
+    }
+  }
+  return 0;
+}
+
+int updi_link_ldcs(PROGRAMMER * pgm, uint8_t address, uint8_t * value) 
+{
+/*
+    def ldcs(self, address):
+        """
+        Load data from Control/Status space
+
+        :param address: address to load
+        """
+        self.logger.debug("LDCS from 0x%02X", address)
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)])
+        response = self.updi_phy.receive(self.LDCS_RESPONSE_BYTES)
+        numbytes_received = len(response)
+        if numbytes_received != self.LDCS_RESPONSE_BYTES:
+            raise PymcuprogError("Unexpected number of bytes in response: "
+                                 "{} byte(s) expected {} byte(s)".format(numbytes_received, self.LDCS_RESPONSE_BYTES))
+
+        return response[0]
+*/
+  unsigned char buffer[2];
+  int result;
+  avrdude_message(MSG_DEBUG, "%s: LDCS from 0x%02X\n", progname, address);
+  buffer[0]=UPDI_PHY_SYNC;
+  buffer[1]=UPDI_LDCS | (address & 0x0F);
+  if (updi_physical_send(pgm, buffer, 2) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LDCS send operation failed\n", progname);
+    return -1;
+  }
+  result = updi_physical_recv(pgm, buffer, 1);
+  if (result != 1) {
+    if (result >= 0) {
+      avrdude_message(MSG_DEBUG, "%s: Incorrect response size, received %d instead of %d bytes\n", progname, result, 1);
+    }
+    return -1;
+  }
+  * value = buffer[0];
+  return 0;
+}
+
+int updi_link_stcs(PROGRAMMER * pgm, uint8_t address, uint8_t value)
+{
+/*
+    def stcs(self, address, value):
+        """
+        Store a value to Control/Status space
+
+        :param address: address to store to
+        :param value: value to write
+        """
+        self.logger.debug("STCS to 0x%02X", address)
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_STCS | (address & 0x0F), value])
+*/
+  unsigned char buffer[3];
+  avrdude_message(MSG_DEBUG, "%s: STCS 0x%02X to address 0x%02X\n", progname, value, address);
+  buffer[0] = UPDI_PHY_SYNC;
+  buffer[1] = UPDI_STCS | (address & 0x0F);
+  buffer[2] = value;
+  return updi_physical_send(pgm, buffer, 3);
+}
+
+int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def ld_ptr_inc(self, size):
+        """
+        Loads a number of bytes from the pointer location with pointer post-increment
+ 
+        :param size: number of bytes to load
+        :return: values read
+        """
+        self.logger.debug("LD8 from ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC |
+                            constants.UPDI_DATA_8])
+        return self.updi_phy.receive(size)
+*/
+  unsigned char send_buffer[2];
+  avrdude_message(MSG_DEBUG, "%s: LD8 from ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LD | UPDI_PTR_INC | UPDI_DATA_8;
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LD_PTR_INC send operation failed\n", progname);
+    return -1;
+  }
+  return updi_physical_recv(pgm, buffer, size);
+}
+
+int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words)
+{
+/*
+    def ld_ptr_inc16(self, words):
+        """
+        Load a 16-bit word value from the pointer location with pointer post-increment
+
+        :param words: number of words to load
+        :return: values read
+        """
+        self.logger.debug("LD16 from ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC |
+                            constants.UPDI_DATA_16])
+        return self.updi_phy.receive(words << 1)
+*/
+  unsigned char send_buffer[2];
+  avrdude_message(MSG_DEBUG, "%s: LD16 from ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LD | UPDI_PTR_INC | UPDI_DATA_16;
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LD_PTR_INC send operation failed\n", progname);
+    return -1;
+  }
+  return updi_physical_recv(pgm, buffer, words << 2);
+}
+
+int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def st_ptr_inc(self, data):
+        """
+        Store data to the pointer location with pointer post-increment
+
+        :param data: data to store
+        """
+        self.logger.debug("ST8 to *ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8,
+                            data[0]])
+        response = self.updi_phy.receive(1)
+
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("ACK error with st_ptr_inc")
+
+        num = 1
+        while num < len(data):
+            self.updi_phy.send([data[num]])
+            response = self.updi_phy.receive(1)
+
+            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+                raise PymcuprogError("Error with st_ptr_inc")
+            num += 1
+*/
+  unsigned char send_buffer[3];
+  unsigned char recv_buffer[1];
+  int response;
+  int num = 1;
+  avrdude_message(MSG_DEBUG, "%s: ST8 to *ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_8;
+  send_buffer[2] = buffer[0];
+  if (updi_physical_send(pgm, send_buffer, 3) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR_INC send operation failed\n", progname);
+    return -1;
+  }
+
+  response = updi_physical_recv(pgm, recv_buffer, 1);
+
+  if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_DEBUG, "%s: ACK was expected but not received\n", progname);
+    return -1;
+  }
+
+  while (num < size) {
+    send_buffer[0]=buffer[num];
+    if (updi_physical_send(pgm, send_buffer, 1) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: ST_PTR_INC data send operation failed\n", progname);
+      return -1;
+    }
+    response = updi_physical_recv(pgm, recv_buffer, 1);
+
+    if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+      avrdude_message(MSG_DEBUG, "%s: Data ACK was expected but not received\n", progname);
+      return -1;
+    }
+    num++;
+  }
+
+  return 0;
+}
+
+int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words)
+{
+/*
+    def st_ptr_inc16(self, data):
+        """
+        Store a 16-bit word value to the pointer location with pointer post-increment
+
+        :param data: data to store
+        """
+        self.logger.debug("ST16 to *ptr++")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |
+                            constants.UPDI_DATA_16, data[0], data[1]])
+        response = self.updi_phy.receive(1)
+
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("ACK error with st_ptr_inc16")
+
+        num = 2
+        while num < len(data):
+            self.updi_phy.send([data[num], data[num + 1]])
+            response = self.updi_phy.receive(1)
+
+            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+                raise PymcuprogError("Error with st_ptr_inc16")
+            num += 2
+*/
+  unsigned char send_buffer[4];
+  unsigned char recv_buffer[1];
+  int response;
+  int num = 2;
+  avrdude_message(MSG_DEBUG, "%s: ST16 to *ptr++\n", progname);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16;
+  send_buffer[2] = buffer[0];
+  send_buffer[3] = buffer[1];
+  if (updi_physical_send(pgm, send_buffer, 4) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR_INC16 send operation failed\n", progname);
+    return -1;
+  }
+
+  response = updi_physical_recv(pgm, recv_buffer, 1);
+
+  if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_DEBUG, "%s: ACK was expected but not received\n", progname);
+    return -1;
+  }
+
+  while (num < words) {
+    send_buffer[0]=buffer[num];
+    send_buffer[1]=buffer[num+1];
+    if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: ST_PTR_INC data send operation failed\n", progname);
+      return -1;
+    }
+    response = updi_physical_recv(pgm, recv_buffer, 1);
+
+    if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
+      avrdude_message(MSG_DEBUG, "%s: Data ACK was expected but not received\n", progname);
+      return -1;
+    }
+    num+=2;
+  }
+
+  return 0;
+}
+
+int updi_link_st_ptr_inc16_RSD(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words, int blocksize) {
+/*
+    def st_ptr_inc16_RSD(self, data, blocksize):
+        """
+        Store a 16-bit word value to the pointer location with pointer post-increment
+        :param data: data to store
+        :blocksize: max number of bytes being sent -1 for all.
+                    Warning: This does not strictly honor blocksize for values < 6
+                    We always glob together the STCS(RSD) and REP commands.
+                    But this should pose no problems for compatibility, because your serial adapter can't deal with 6b chunks,
+                    none of pymcuprog would work!
+        """
+        self.logger.debug("ST16 to *ptr++ with RSD, data length: 0x%03X in blocks of:  %d", len(data), blocksize)
+
+        #for performance we glob everything together into one USB transfer....
+        repnumber= ((len(data) >> 1) -1)
+        data = [*data, *[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA, 0x06]]
+
+        if blocksize == -1 :
+            # Send whole thing at once stcs + repeat + st + (data + stcs)
+            blocksize = 3 + 3 + 2 + len(data)
+        num = 0
+        firstpacket = []
+        if blocksize < 10 :
+            # very small block size - we send pair of 2-byte commands first.
+            firstpacket = [*[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA, 0x0E],
+                            *[constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, (repnumber & 0xFF)]]
+            data = [*[constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |constants.UPDI_DATA_16], *data]
+            num = 0
+        else:
+            firstpacket = [*[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA , 0x0E],
+                            *[constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, (repnumber & 0xFF)],
+                            *[constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_16],
+                            *data[:blocksize - 8]]
+            num = blocksize - 8
+        self.updi_phy.send( firstpacket )
+
+        # if finite block size, this is used.
+        while num < len(data):
+            data_slice = data[num:num+blocksize]
+            self.updi_phy.send(data_slice)
+            num += len(data_slice)
+*/
+  avrdude_message(MSG_DEBUG, "%s: ST16 to *ptr++ with RSD, data length: 0x%03X in blocks of: %d\n", progname, words * 2, blocksize);
+
+  unsigned int temp_buffer_size = 3 + 3 + 2 + (words * 2) + 3;
+  unsigned int num=0;
+  unsigned char* temp_buffer = malloc(temp_buffer_size);
+
+  if (temp_buffer == 0) {
+    avrdude_message(MSG_DEBUG, "%s: Allocating temporary buffer failed\n", progname);
+    return -1;
+  }
+
+  if (blocksize == -1) {
+    blocksize = temp_buffer_size;
+  }
+
+  temp_buffer[0] = UPDI_PHY_SYNC;
+  temp_buffer[1] = UPDI_STCS | UPDI_CS_CTRLA;
+  temp_buffer[2] = 0x0E;
+  temp_buffer[3] = UPDI_PHY_SYNC;
+  temp_buffer[4] = UPDI_REPEAT | UPDI_REPEAT_BYTE;
+  temp_buffer[5] = (words - 1) & 0xFF;
+  temp_buffer[6] = UPDI_PHY_SYNC;
+  temp_buffer[7] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16;
+
+  memcpy(temp_buffer + 8, buffer, words * 2);
+
+  temp_buffer[temp_buffer_size-3] = UPDI_PHY_SYNC;
+  temp_buffer[temp_buffer_size-2] = UPDI_STCS | UPDI_CS_CTRLA;
+  temp_buffer[temp_buffer_size-1] = 0x06;
+
+  if (blocksize < 10) {
+    if (updi_physical_send(pgm, temp_buffer, 6) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Failed to send first package\n", progname);
+      free(temp_buffer);
+      return -1;
+    }
+    num = 6;
+  } 
+
+  while (num < temp_buffer_size) {
+    int next_package_size;
+
+    if (num + blocksize > temp_buffer_size) {
+      next_package_size = temp_buffer_size - num;
+    } else {
+      next_package_size = blocksize;
+    }
+
+    if (updi_physical_send(pgm, temp_buffer + num, next_package_size) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Failed to send package\n", progname);
+      free(temp_buffer);
+      return -1;
+    }
+
+    num+=next_package_size;
+  }
+  free(temp_buffer);
+  return 0;
+}
+
+int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats)
+{
+/*
+    def repeat(self, repeats):
+        """
+        Store a value to the repeat counter
+
+        :param repeats: number of repeats requested
+        """
+        self.logger.debug("Repeat %d", repeats)
+        if (repeats - 1) > constants.UPDI_MAX_REPEAT_SIZE:
+            self.logger.error("Invalid repeat count of %d", repeats)
+            raise Exception("Invalid repeat count!")
+        repeats -= 1
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE,
+                            repeats & 0xFF])
+*/
+  unsigned char buffer[3];
+  avrdude_message(MSG_DEBUG, "%s: Repeat %d\n", progname, repeats);
+  if ((repeats - 1) > UPDI_MAX_REPEAT_SIZE) {
+    avrdude_message(MSG_DEBUG, "%s: Invalid repeat count of %d\n", progname, repeats);
+    return -1;
+  }
+  repeats-=1;
+  buffer[0] = UPDI_PHY_SYNC;
+  buffer[1] = UPDI_REPEAT | UPDI_REPEAT_BYTE;
+  buffer[2] = repeats & 0xFF;
+  return updi_physical_send(pgm, buffer, 3);
+}
+
+int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def read_sib(self):
+        """
+        Read the SIB
+        """
+        return self.updi_phy.sib()
+*/
+  return updi_physical_sib(pgm, buffer, size);
+}
+
+int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size)
+{
+/*
+    def key(self, size, key):
+        """
+        Write a key
+ 
+        :param size: size of key (0=64B, 1=128B, 2=256B)
+        :param key: key value
+        """
+        self.logger.debug("Writing key")
+        if len(key) != 8 << size:
+            raise PymcuprogError("Invalid KEY length!")
+        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_KEY | constants.UPDI_KEY_KEY | size])
+        self.updi_phy.send(list(reversed(list(key))))
+*/
+  unsigned char send_buffer[2];
+  unsigned char reversed_key[256];
+  int index;
+  avrdude_message(MSG_DEBUG, "%s: UPDI writing key\n", progname);
+  if (size != (8 << size_type)) {
+    avrdude_message(MSG_DEBUG, "%s: Invalid key length\n", progname);
+    return -1;
+  }
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_KEY | UPDI_KEY_KEY | size_type;
+  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI key send message failed\n", progname);
+    return -1;
+  }
+  /* reverse key contents */
+  for (index=0; index<size; index++) {
+    reversed_key[index] = buffer[size-index-1];
+  }
+  return updi_physical_send(pgm, reversed_key, size);
+}
+
+int updi_link_ld(PROGRAMMER * pgm, uint32_t address, uint8_t * value)
+{
+/*
+    def ld(self, address):
+        """
+        Load a single byte direct from a 24-bit address
+
+        :param address: address to load from
+        :return: value read
+        """
+        self.logger.info("LD from 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self.updi_phy.receive(1)[0]
+*/
+  unsigned char send_buffer[5];
+  unsigned char recv_buffer[1];
+  avrdude_message(MSG_DEBUG, "%s: LD from 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LDS | UPDI_DATA_8 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LD operation send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LD operation recv failed\n", progname);
+    return -1;
+  }
+  * value = recv_buffer[0];
+  return 0;
+}
+
+int updi_link_ld16(PROGRAMMER * pgm, uint32_t address, uint16_t * value)
+{
+/*
+    def ld16(self, address):
+        """
+        Load a 16-bit word directly from a 24-bit address
+
+        :param address: address to load from
+        :return: values read
+        """
+        self.logger.info("LD from 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self.updi_phy.receive(2)
+*/
+  unsigned char send_buffer[5];
+  unsigned char recv_buffer[2];
+  avrdude_message(MSG_DEBUG, "%s: LD16 from 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_LDS | UPDI_DATA_16 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LD16 operation send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 2) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: LD16 operation recv failed\n", progname);
+    return -1;
+  }
+  * value = (recv_buffer[0] << 8 | recv_buffer[1]);
+  return 0;
+}
+
+static int updi_link_st_data_phase(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size)
+{
+/*
+    def _st_data_phase(self, values):
+        """
+        Performs data phase of transaction:
+        * receive ACK
+        * send data
+
+        :param values: bytearray of value(s) to send
+        """
+        response = self.updi_phy.receive(1)
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("Error with st")
+
+        self.updi_phy.send(values)
+        response = self.updi_phy.receive(1)
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("Error with st")
+*/
+  unsigned char recv_buffer[1];
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI data phase recv failed on first ACK\n", progname);
+    return -1;
+  }
+  if (recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI data phase expected first ACK\n", progname);
+    return -1;
+  }
+  if (updi_physical_send(pgm, buffer, size) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI data phase send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI data phase recv failed on second ACK\n", progname);
+    return -1;
+  }
+  if (recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI data phase expected second ACK\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+int updi_link_st(PROGRAMMER * pgm, uint32_t address, uint8_t value)
+{
+/*
+    def st(self, address, value):
+        """
+        Store a single byte value directly to a 24-bit address
+
+        :param address: address to write to
+        :param value: value to write
+        """
+        self.logger.info("ST to 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self._st_data_phase([value & 0xFF])
+*/
+  unsigned char send_buffer[5];
+  avrdude_message(MSG_DEBUG, "%s: ST to 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_STS | UPDI_DATA_8 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST operation send failed\n", progname);
+    return -1;
+  }
+  send_buffer[0] = value;
+  return updi_link_st_data_phase(pgm, send_buffer, 1);
+}
+
+int updi_link_st16(PROGRAMMER * pgm, uint32_t address, uint16_t value)
+{
+/*
+    def st16(self, address, value):
+        """
+        Store a 16-bit word value directly to a 24-bit address
+
+        :param address: address to write to
+        :param value: value to write
+        """
+        self.logger.info("ST to 0x{0:06X}".format(address))
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        return self._st_data_phase([value & 0xFF, (value >> 8) & 0xFF])
+*/
+  unsigned char send_buffer[5];
+  avrdude_message(MSG_DEBUG, "%s: ST16 to 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_STS | UPDI_DATA_16 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST16 operation send failed\n", progname);
+    return -1;
+  }
+  send_buffer[0] = value & 0xFF;
+  send_buffer[1] = (value >> 8) & 0xFF;
+  return updi_link_st_data_phase(pgm, send_buffer, 2);
+}
+
+int updi_link_st_ptr(PROGRAMMER * pgm, uint32_t address)
+{
+/*
+    def st_ptr(self, address):
+        """
+        Set the pointer location
+
+        :param address: address to write
+        """
+        self.logger.info("ST to ptr")
+        self.updi_phy.send(
+            [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_24,
+             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
+        response = self.updi_phy.receive(1)
+        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
+            raise PymcuprogError("Error with st_ptr")
+*/
+  unsigned char send_buffer[5];
+  unsigned char recv_buffer[1];
+  avrdude_message(MSG_DEBUG, "%s: ST_PTR to 0x%06X\n", progname, address);
+  send_buffer[0] = UPDI_PHY_SYNC;
+  send_buffer[1] = UPDI_STS | UPDI_ST | UPDI_PTR_ADDRESS | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_DATA_24 : UPDI_DATA_16);
+  send_buffer[2] = address & 0xFF;
+  send_buffer[3] = (address >> 8) & 0xFF;
+  send_buffer[4] = (address >> 16) & 0xFF;
+  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR operation send failed\n", progname);
+    return -1;
+  }
+  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI ST_PTR recv failed on ACK\n", progname);
+    return -1;
+  }
+  if (recv_buffer[0] != UPDI_PHY_ACK) {
+    avrdude_message(MSG_DEBUG, "%s: UPDI ST_PTR expected ACK\n", progname);
+    return -1;
+  }
+  return 0;
+}
diff --git a/updi_link.h b/updi_link.h
new file mode 100644
index 00000000..5b5e5bda
--- /dev/null
+++ b/updi_link.h
@@ -0,0 +1,59 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_link_h
+#define updi_link_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int updi_link_open(PROGRAMMER * pgm);
+void updi_link_close(PROGRAMMER * pgm);
+int updi_link_init(PROGRAMMER * pgm);
+int updi_link_ldcs(PROGRAMMER * pgm, uint8_t address, uint8_t * value);
+int updi_link_stcs(PROGRAMMER * pgm, uint8_t address, uint8_t value);
+int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
+int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
+int updi_link_st_ptr_inc16_RSD(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words, int blocksize);
+int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats);
+int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
+int updi_link_ld(PROGRAMMER * pgm, uint32_t address, uint8_t * value);
+int updi_link_ld16(PROGRAMMER * pgm, uint32_t address, uint16_t * value);
+int updi_link_st(PROGRAMMER * pgm, uint32_t address, uint8_t value);
+int updi_link_st16(PROGRAMMER * pgm, uint32_t address, uint16_t value);
+int updi_link_st_ptr(PROGRAMMER * pgm, uint32_t address);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_link_h */
diff --git a/updi_nvm.c b/updi_nvm.c
new file mode 100644
index 00000000..f3d899a9
--- /dev/null
+++ b/updi_nvm.c
@@ -0,0 +1,1275 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "updi_nvm.h"
+#include "updi_state.h"
+#include "updi_constants.h"
+#include "updi_readwrite.h"
+
+typedef enum 
+{
+  DONT_USE_WORD_ACCESS,
+  USE_WORD_ACCESS
+} access_mode;
+
+#define USE_DEFAULT_COMMAND 0xFF
+
+static int nvm_chip_erase_V0(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def chip_erase(self):
+        """
+        Does a chip erase using the NVM controller
+
+        Note that on locked devices this is not possible
+        and the ERASE KEY has to be used instead, see the unlock method
+        """
+        self.logger.info("Chip erase using NVM CTRL")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before chip erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after chip erase")
+
+        return True
+*/
+  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_flash_page_V0(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
+{
+/*
+    def erase_flash_page(self, address):
+        """
+        Erasing single flash page using the NVM controller (v0)
+
+        :param address: Start address of page to erase
+        :type address: int
+        """
+        self.logger.info("Erase flash page at address 0x%08X", address)
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
+
+        # Dummy write
+        self.readwrite.write_data(address, [0xFF])
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
+*/
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0] = 0xFF;
+  if (updi_write_data(pgm, address, data, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_eeprom_V0(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def erase_eeprom(self):
+        """
+        Erase EEPROM memory only (v0)
+        """
+        self.logger.info("Erase EEPROM")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
+*/  
+  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_user_row_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+/*
+    def erase_user_row(self, address, size):
+        """
+        Erase User Row memory only (v0)
+
+        :param address: Start address of user row
+        :type address: int
+        """
+        self.logger.info("Erase user row")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before user row erase")
+
+        # On this NVM version user row is implemented as EEPROM
+        # When erasing single EEPROM pages a dummy write is needed for each location to be erased
+        for offset in range(size):
+            self.readwrite.write_data(address+offset, [0xFF])
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after user row erase")
+*/
+  uint16_t offset;
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase user row\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0]=0xFF;
+  for (offset = 0; offset<size; offset++)
+  {
+    if (updi_write_data(pgm, address+offset, data, 1) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed at offset 0x%04x\n", progname, offset);
+      return -1;
+    }
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Erase page operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_write_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command);
+
+static int nvm_write_eeprom_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+
+
+static int nvm_write_flash_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_flash(self, address, data):
+        """
+        Writes data to flash (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V0(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
+}
+
+static int nvm_write_user_row_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_user_row(self, address, data):
+        """
+        Writes data to user row (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # On this NVM variant user row is implemented as EEPROM
+        return self.write_eeprom(address, data)
+*/
+  return nvm_write_eeprom_V0(pgm, p, address, buffer, size);
+}
+
+static int nvm_write_eeprom_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_eeprom(self, address, data):
+        """
+        Write data to EEPROM (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=False,
+                              nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE)
+*/
+  return nvm_write_V0(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS, UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE);
+}
+
+static int nvm_write_fuse_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+/*
+    def write_fuse(self, address, data):
+        """
+        Writes one fuse value (v0)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready before fuse write")
+
+        # Write address to NVMCTRL ADDR
+        self.logger.debug("Load NVM address")
+        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL, address & 0xFF)
+        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF)
+
+        # Write data
+        self.logger.debug("Load fuse data")
+        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL, data[0] & 0xFF)
+
+        # Execute
+        self.logger.debug("Execute fuse write")
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE)
+
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after fuse write")
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Load NVM address\n", progname);
+  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_ADDRL, address & 0xFF) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write ADDRL operation failed\n", progname);
+    return -1;
+  }
+  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write ADDRH operation failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Load fuse data\n", progname);
+  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_DATAL, value & 0xFF) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write DATAL operation failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Execute fuse write\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write fuse operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_write_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command)
+{
+/*
+    def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE):
+        """
+        Writes a page of data to NVM (v0)
+
+        By default the PAGE_WRITE command is used, which
+        requires that the page is already erased.
+        By default word access is used (flash)
+
+        :param address: address to write to
+        :param data: data to write
+        :param use_word_access: write whole words?
+        :param nvmcommand: command to use for commit
+        """
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear")
+
+        # Clear the page buffer
+        self.logger.debug("Clear page buffer")
+        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR)
+
+        # Wait for NVM controller to be ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear")
+
+        # Load the page buffer by writing directly to location
+        if use_word_access:
+            self.readwrite.write_data_words(address, data)
+        else:
+            self.readwrite.write_data(address, data)
+
+        # Write the page to NVM, maybe erase first
+        self.logger.debug("Committing data")
+        self.execute_nvm_command(nvmcommand)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write")
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear page buffer\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (mode == USE_WORD_ACCESS) {
+    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
+      return -1;
+    }
+  } else {
+    if (updi_write_data(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+      return -1;
+    }
+  }
+  avrdude_message(MSG_DEBUG, "%s: Committing data\n", progname);
+  if (nvm_command == USE_DEFAULT_COMMAND) {
+    nvm_command = UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE;
+  }
+  if (updi_nvm_command(pgm, p, nvm_command) < 0) {
+      avrdude_message(MSG_INFO, "%s: Commit data command failed\n", progname);
+      return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_chip_erase_V2(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def chip_erase(self):
+        """
+        Does a chip erase using the NVM controller
+        Note that on locked devices this it not possible
+        and the ERASE KEY has to be used instead
+        """
+        self.logger.info("Chip erase using NVM CTRL")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready before chip erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready after chip erase")
+
+        return True
+*/
+  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_flash_page_V2(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
+{
+/*
+    def erase_flash_page(self, address):
+        """
+        Erasing single flash page using the NVM controller (v1)
+
+        :param address: Start address of page to erase
+        :type address: int
+        """
+        self.logger.info("Erase flash page at address 0x%08X", address)
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
+
+        # Erase command
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE)
+
+        # Dummy write
+        self.readwrite.write_data(address, [0xFF])
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
+
+        # Remove command from NVM controller
+        self.logger.debug("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0] = 0xFF;
+  if (updi_write_data(pgm, address, data, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_eeprom_V2(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def erase_eeprom(self):
+        """
+        Erase EEPROM memory only (v1)
+        """
+        self.logger.info("Erase EEPROM")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE)
+
+        # And wait for it
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
+
+        # Remove command from NVM controller
+        self.logger.debug("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_user_row_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+/*
+    def erase_user_row(self, address, size):
+        """
+        Erase User Row memory only (v1)
+
+        :param address: Start address of user row
+        :type address: int
+        """
+        # size is not used for this NVM version
+        _dummy = size
+        # On this NVM version user row is implemented as flash
+        return self.erase_flash_page(address)
+*/
+  return nvm_erase_flash_page_V2(pgm, p, address);
+}
+
+static int nvm_write_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode);
+
+static int nvm_write_flash_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_flash(self, address, data):
+        """
+        Writes data to flash (v1)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V2(pgm, p, address, buffer, size, USE_WORD_ACCESS);
+}
+
+static int nvm_write_user_row_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_user_row(self, address, data):
+        """
+        Writes data to user row (v1)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # On this NVM variant user row is implemented as Flash
+        return self.write_nvm(address, data, use_word_access=False)
+*/
+  return nvm_write_V2(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS);
+}
+
+static int nvm_write_eeprom_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_eeprom(self, address, data):
+        """
+        Writes data to NVM (EEPROM)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM ready before command write")
+
+        # Write the command to the NVM controller
+        self.logger.info("NVM EEPROM erase/write command")
+        self.execute_nvm_command(nvm_command)
+
+        # Write the data
+        self.readwrite.write_data(address, data)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM ready after data write")
+
+        # Remove command from NVM controller
+        self.logger.info("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: NVM EEPROM erase/write command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_write_data(pgm, address, buffer, size) < 0) {
+    avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear NVM command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_write_fuse_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+/*
+    def write_fuse(self, address, data):
+        """
+        Writes one fuse value
+        V1 fuses are EEPROM-based
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_eeprom(address, data)
+*/
+  unsigned char buffer[1];
+  buffer[0]=value;
+  return nvm_write_eeprom_V2(pgm, p, address, buffer, 1);
+}
+
+static int nvm_write_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode)
+{
+/*
+    def write_nvm(self, address, data, use_word_access):
+        """
+        Writes data to NVM (version 1)
+        This version of the NVM block has no page buffer, so words are written directly.
+
+        :param address: address to write to
+        :param data: data to write
+        :param use_word_access: write in whole words?
+        """
+        nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready before page buffer clear")
+
+        # Write the command to the NVM controller
+        self.logger.info("NVM write command")
+        self.execute_nvm_command(nvm_command)
+
+        # Write the data
+        if use_word_access:
+            self.readwrite.write_data_words(address, data)
+        else:
+            self.readwrite.write_data(address, data)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise Exception("Timeout waiting for NVM controller to be ready after data write")
+
+        # Remove command from NVM controller
+        self.logger.info("Clear NVM command")
+        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: NVM write command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
+    return -1;
+  }
+  if (mode == USE_WORD_ACCESS) {
+    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
+      return -1;
+    }
+  } else {
+    if (updi_write_data(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+      return -1;
+    }
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear NVM command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_chip_erase_V3(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def chip_erase(self):
+        """
+        Does a chip erase using the NVM controller
+
+        Note that on locked devices this is not possible
+        and the ERASE KEY has to be used instead, see the unlock method
+        """
+        self.logger.info("Chip erase using NVM CTRL")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before chip erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE)
+
+        # And wait for it
+        status = self.wait_nvm_ready()
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+
+        if not status:
+            raise IOError("Timeout waiting for NVM controller to be ready after chip erase")
+
+        return True
+*/
+  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_flash_page_V3(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
+{
+/*
+    def erase_flash_page(self, address):
+        """
+        Erasing single flash page using the NVM controller (v3)
+
+        :param address: Start address of page to erase
+        :type address: int
+        """
+        self.logger.info("Erase flash page at address 0x%08X", address)
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
+
+        # Dummy write
+        self.readwrite.write_data(address, [0xFF])
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE)
+
+        # And wait for it
+        status = self.wait_nvm_ready()
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+
+        if not status:
+            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
+*/  
+  unsigned char data[1];
+  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  data[0] = 0xFF;
+  if (updi_write_data(pgm, address, data, 1) < 0) {
+    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_eeprom_V3(PROGRAMMER * pgm, AVRPART * p)
+{
+/*
+    def erase_eeprom(self):
+        """
+        Erase EEPROM memory only
+        """
+        self.logger.info("Erase EEPROM")
+
+        # Wait until NVM CTRL is ready to erase
+        if not self.wait_nvm_ready():
+            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
+
+        # Erase
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE)
+
+        # And wait for it
+        status = self.wait_nvm_ready()
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+
+        if not status:
+            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
+*/
+  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE) < 0) {
+    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+static int nvm_erase_user_row_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+/*
+    def erase_user_row(self, address, size):
+        """
+        Erase User Row memory only
+
+        :param address: Start address of user row
+        :type address: int
+        """
+        self.logger.info("Erase user row")
+
+        # On this NVM version user row is implemented as FLASH
+        return self.erase_flash_page(self, address)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Erase user row at address 0x%06X\n", progname, address);
+  
+  return nvm_erase_flash_page_V3(pgm, p, address);
+}
+
+static int nvm_write_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command);
+
+static int nvm_write_flash_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_flash(self, address, data):
+        """
+        Writes data to flash (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V3(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
+}
+
+static int nvm_write_user_row_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_user_row(self, address, data):
+        """
+        Writes data to user row (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # On this NVM variant user row is implemented as FLASH
+        return self.write_nvm(address, data, use_word_access=True)
+*/
+  return nvm_write_V3(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
+}
+
+static int nvm_write_eeprom_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+/*
+    def write_eeprom(self, address, data):
+        """
+        Write data to EEPROM (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_nvm(address, data, use_word_access=False,
+                              nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE)
+*/
+  return nvm_write_V3(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS, UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE);
+}
+
+static int nvm_write_fuse_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+/*
+    def write_fuse(self, address, data):
+        """
+        Writes one fuse value (v3)
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        return self.write_eeprom(address, data)
+*/
+  unsigned char buffer[1];
+  buffer[0] = value;
+  return nvm_write_eeprom_V3(pgm, p, address, buffer, 1);
+}
+
+static int nvm_write_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
+                        uint16_t size, access_mode mode, uint8_t nvm_command)
+{
+/*
+    def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE):
+        """
+        Writes a page of data to NVM (v3)
+
+        By default the PAGE_WRITE command is used, which
+        requires that the page is already erased.
+        By default word access is used (flash)
+
+        :param address: address to write to
+        :param data: data to write
+        :param use_word_access: write whole words?
+        :param nvmcommand: command to use for commit
+        """
+
+        # Check that NVM controller is ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear")
+
+        # Clear the page buffer
+        self.logger.debug("Clear page buffer")
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR)
+
+        # Wait for NVM controller to be ready
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear")
+
+        # Load the page buffer by writing directly to location
+        if use_word_access:
+            self.readwrite.write_data_words(address, data)
+        else:
+            self.readwrite.write_data(address, data)
+
+        # Write the page to NVM, maybe erase first
+        self.logger.debug("Committing data")
+        self.execute_nvm_command(nvmcommand)
+
+        # Wait for NVM controller to be ready again
+        if not self.wait_nvm_ready():
+            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write")
+
+        # Remove command
+        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
+*/
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  avrdude_message(MSG_DEBUG, "%s: Clear page buffer\n", progname);
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR) < 0) {
+    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (mode == USE_WORD_ACCESS) {
+    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
+      return -1;
+    }
+  } else {
+    if (updi_write_data(pgm, address, buffer, size) < 0) {
+      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
+      return -1;
+    }
+  }
+  avrdude_message(MSG_DEBUG, "%s: Committing data\n", progname);
+  if (nvm_command == USE_DEFAULT_COMMAND) {
+    nvm_command = UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE;
+  }
+  if (updi_nvm_command(pgm, p, nvm_command) < 0) {
+      avrdude_message(MSG_INFO, "%s: Commit data command failed\n", progname);
+      return -1;
+  }
+  if (updi_nvm_wait_ready(pgm, p) < 0) {
+    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
+    return -1;
+  }
+  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
+    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
+    return -1;
+  }
+  return 0;
+}
+
+
+int updi_nvm_chip_erase(PROGRAMMER * pgm, AVRPART * p)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_chip_erase_V0(pgm, p);
+    case UPDI_NVM_MODE_V2:
+      return nvm_chip_erase_V2(pgm, p);
+    case UPDI_NVM_MODE_V3:
+      return nvm_chip_erase_V3(pgm, p);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_erase_flash_page(PROGRAMMER * pgm, AVRPART *p, uint32_t address)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_erase_flash_page_V0(pgm, p, address);
+    case UPDI_NVM_MODE_V2:
+      return nvm_erase_flash_page_V2(pgm, p, address);
+    case UPDI_NVM_MODE_V3:
+      return nvm_erase_flash_page_V3(pgm, p, address);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_erase_eeprom(PROGRAMMER * pgm, AVRPART *p)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_erase_eeprom_V0(pgm, p);
+    case UPDI_NVM_MODE_V2:
+      return nvm_erase_eeprom_V2(pgm, p);
+    case UPDI_NVM_MODE_V3:
+      return nvm_erase_eeprom_V3(pgm, p);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_erase_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_erase_user_row_V0(pgm, p, address, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_erase_user_row_V2(pgm, p, address, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_erase_user_row_V3(pgm, p, address, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_flash(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_flash_V0(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_flash_V2(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_flash_V3(pgm, p, address, buffer, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_user_row_V0(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_user_row_V2(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_user_row_V3(pgm, p, address, buffer, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_eeprom(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_eeprom_V0(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_eeprom_V2(pgm, p, address, buffer, size);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_eeprom_V3(pgm, p, address, buffer, size);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_write_fuse(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
+{
+  switch(updi_get_nvm_mode(pgm))
+  {
+    case UPDI_NVM_MODE_V0:
+      return nvm_write_fuse_V0(pgm, p, address, value);
+    case UPDI_NVM_MODE_V2:
+      return nvm_write_fuse_V2(pgm, p, address, value);
+    case UPDI_NVM_MODE_V3:
+      return nvm_write_fuse_V3(pgm, p, address, value);
+    default:
+      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
+      return -1;
+  }
+}
+
+int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p)
+{
+/*
+    def wait_nvm_ready(self):
+        """
+        Waits for the NVM controller to be ready
+        """
+        timeout = Timeout(10000)  # 10 sec timeout, just to be sure
+
+        self.logger.debug("Wait NVM ready")
+        while not timeout.expired():
+            status = self.readwrite.read_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_STATUS)
+            if status & (1 << constants.UPDI_NVM_STATUS_WRITE_ERROR):
+                self.logger.error("NVM error")
+                return False
+
+            if not status & ((1 << constants.UPDI_NVM_STATUS_EEPROM_BUSY) |
+                             (1 << constants.UPDI_NVM_STATUS_FLASH_BUSY)):
+                return True
+
+        self.logger.error("Wait NVM ready timed out")
+        return False
+*/
+  unsigned long start_time;
+  unsigned long current_time;
+  struct timeval tv;
+  uint8_t status;
+  gettimeofday (&tv, NULL);
+  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  do {
+    if (updi_read_byte(pgm, p->nvm_base + UPDI_NVMCTRL_STATUS, &status) >= 0) {
+      if (status & (1 << UPDI_NVM_STATUS_WRITE_ERROR)) {
+        avrdude_message(MSG_INFO, "%s: NVM error\n", progname);
+        return -1;
+      }
+      if (!(status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | 
+                      (1 << UPDI_NVM_STATUS_FLASH_BUSY)))) {
+        return 0;
+      }
+    }
+    gettimeofday (&tv, NULL);
+    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
+  } while ((current_time - start_time) < 10000000);
+
+  avrdude_message(MSG_INFO, "%s: Wait NVM ready timed out\n", progname);
+  return -1;
+}
+
+int updi_nvm_command(PROGRAMMER * pgm, AVRPART *p, uint8_t command)
+{
+/*
+    def execute_nvm_command(self, command):
+        """
+        Executes an NVM COMMAND on the NVM CTRL
+
+        :param command: command to execute
+        """
+        self.logger.debug("NVMCMD %d executing", command)
+        return self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_CTRLA, command)
+*/
+  avrdude_message(MSG_DEBUG, "%s: NVMCMD %d executing\n", progname, command);
+
+  return updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_CTRLA, command);
+}
diff --git a/updi_nvm.h b/updi_nvm.h
new file mode 100644
index 00000000..c0c1544e
--- /dev/null
+++ b/updi_nvm.h
@@ -0,0 +1,51 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_nvm_h
+#define updi_nvm_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int updi_nvm_chip_erase(PROGRAMMER * pgm, AVRPART * p);
+int updi_nvm_erase_flash_page(PROGRAMMER * pgm, AVRPART *p, uint32_t address);
+int updi_nvm_erase_eeprom(PROGRAMMER * pgm, AVRPART *p);
+int updi_nvm_erase_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size);
+int updi_nvm_write_flash(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+int updi_nvm_write_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+int updi_nvm_write_eeprom(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
+int updi_nvm_write_fuse(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value);
+int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p);
+int updi_nvm_command(PROGRAMMER * pgm, AVRPART *p, uint8_t command);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_nvm_h */
diff --git a/updi_readwrite.c b/updi_readwrite.c
new file mode 100644
index 00000000..3ee19cb0
--- /dev/null
+++ b/updi_readwrite.c
@@ -0,0 +1,316 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "avrdude.h"
+#include "libavrdude.h"
+#include "updi_constants.h"
+#include "updi_link.h"
+#include "updi_readwrite.h"
+
+int updi_read_cs(PROGRAMMER * pgm, uint8_t address, uint8_t * value)
+{
+/*
+    def read_cs(self, address):
+        """
+        Read from Control/Status space
+
+        :param address: address (index) to read
+        :return: value read
+        """
+        return self.datalink.ldcs(address)
+*/
+  return updi_link_ldcs(pgm, address, value);
+}
+
+int updi_write_cs(PROGRAMMER * pgm, uint8_t address, uint8_t value)
+{
+/*
+    def write_cs(self, address, value):
+        """
+        Write to Control/Status space
+
+        :param address: address (index) to write
+        :param value: 8-bit value to write
+        """
+        return self.datalink.stcs(address, value)
+*/
+  return updi_link_stcs(pgm, address, value);
+}
+
+int updi_write_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size)
+{
+/*
+    def write_key(self, size, key):
+        """
+        Write a KEY into UPDI
+
+        :param size: size of key to send
+        :param key: key value
+        """
+        return self.datalink.key(size, key)
+*/
+  return updi_link_key(pgm, buffer, size_type, size);
+}
+
+int updi_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
+{
+/*
+    def read_sib(self):
+        """
+        Read the SIB from UPDI
+
+        :return: SIB string (bytearray) read
+        """
+        return self.datalink.read_sib()
+*/
+  return updi_link_read_sib(pgm, buffer, size);
+}
+
+int updi_read_byte(PROGRAMMER * pgm, uint32_t address, uint8_t * value)
+{
+/*
+    def read_byte(self, address):
+        """
+        Read a single byte from UPDI
+
+        :param address: address to read from
+        :return: value read
+        """
+        return self.datalink.ld(address)
+*/
+  return updi_link_ld(pgm, address, value);
+}
+
+int updi_write_byte(PROGRAMMER * pgm, uint32_t address, uint8_t value)
+{
+/*
+    def write_byte(self, address, value):
+        """
+        Writes a single byte to UPDI
+
+        :param address: address to write to
+        :param value: value to write
+        """
+        return self.datalink.st(address, value)
+*/
+  return updi_link_st(pgm, address, value);
+}
+
+int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def read_data(self, address, size):
+        """
+        Reads a number of bytes of data from UPDI
+
+        :param address: address to write to
+        :param size: number of bytes to read
+        """
+        self.logger.debug("Reading %d bytes from 0x%04X", size, address)
+        # Range check
+        if size > constants.UPDI_MAX_REPEAT_SIZE:
+            raise PymcuprogError("Cant read that many bytes in one go")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        if size > 1:
+            self.datalink.repeat(size)
+
+        # Do the read(s)
+        return self.datalink.ld_ptr_inc(size)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Reading %d bytes from 0x%06X\n", progname, size, address);
+
+  if (size > UPDI_MAX_REPEAT_SIZE) {
+    avrdude_message(MSG_DEBUG, "%s: Can't read that many bytes in one go\n", progname);
+    return -1;
+  }
+
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+
+  if (size > 1) {
+    if (updi_link_repeat(pgm, size) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Repeat operation failed\n", progname);
+      return -1;
+    }
+  }
+  return updi_link_ld_ptr_inc(pgm, buffer, size);
+}
+
+int updi_write_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def write_data(self, address, data):
+        """
+        Writes a number of bytes to memory
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # Special case of 1 byte
+        if len(data) == 1:
+            return self.datalink.st(address, data[0])
+        # Special case of 2 byte
+        if len(data) == 2:
+            self.datalink.st(address, data[0])
+            return self.datalink.st(address + 1, data[1])
+
+        # Range check
+        if len(data) > constants.UPDI_MAX_REPEAT_SIZE:
+            raise PymcuprogError("Invalid length")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        self.datalink.repeat(len(data))
+        return self.datalink.st_ptr_inc(data)
+*/
+  if (size == 1) {
+    return updi_link_st(pgm, address, buffer[0]);
+  }
+  if (size == 2) {
+    if (updi_link_st(pgm, address, buffer[0]) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: ST operation failed\n", progname);
+      return -1;
+    }
+    return updi_link_st(pgm, address+1, buffer[1]);
+  }
+  if (size > UPDI_MAX_REPEAT_SIZE) {
+    avrdude_message(MSG_DEBUG, "%s: Invalid length\n", progname);
+    return -1;
+  }
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+  if (updi_link_repeat(pgm, size) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: Repeat operation failed\n", progname);
+    return -1;
+  }
+  return updi_link_st_ptr_inc(pgm, buffer, size);
+}
+
+int updi_read_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def read_data_words(self, address, words):
+        """
+        Reads a number of words of data from UPDI
+
+        :param address: address to write to
+        :param words: number of words to read
+        """
+        self.logger.debug("Reading %d words from 0x%04X", words, address)
+
+        # Range check
+        if words > constants.UPDI_MAX_REPEAT_SIZE >> 1:
+            raise PymcuprogError("Cant read that many words in one go")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        if words > 1:
+            self.datalink.repeat(words)
+
+        # Do the read
+        return self.datalink.ld_ptr_inc16(words)
+*/
+  avrdude_message(MSG_DEBUG, "%s: Reading %d words from 0x%06X", progname, size, address);
+
+  if (size > (UPDI_MAX_REPEAT_SIZE >> 1)) {
+    avrdude_message(MSG_DEBUG, "%s: Can't read that many words in one go\n", progname);
+    return -1;
+  }
+
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+
+  if (size > 1) {
+    if (updi_link_repeat(pgm, size) < 0) {
+      avrdude_message(MSG_DEBUG, "%s: Repeat operation failed\n", progname);
+      return -1;
+    }
+  }
+  return updi_link_ld_ptr_inc16(pgm, buffer, size);
+}
+
+int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
+{
+/*
+    def write_data_words(self, address, data):
+        """
+        Writes a number of words to memory
+
+        :param address: address to write to
+        :param data: data to write
+        """
+        # Special-case of 1 word
+        if len(data) == 2:
+            value = data[0] + (data[1] << 8)
+            return self.datalink.st16(address, value)
+
+        # Range check
+        if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1:
+            raise PymcuprogError("Invalid length")
+
+        # Store the address
+        self.datalink.st_ptr(address)
+
+        # Fire up the repeat
+        self.datalink.repeat(len(data) >> 1)
+        return self.datalink.st_ptr_inc16(data)
+*/
+  if (size == 2) {
+    return updi_link_st16(pgm, address, buffer[0] + (buffer[1] << 8));
+  }
+  if (size > UPDI_MAX_REPEAT_SIZE << 1) {
+    avrdude_message(MSG_DEBUG, "%s: Invalid length\n", progname);
+    return -1;
+  }
+  if (updi_link_st_ptr(pgm, address) < 0) {
+    avrdude_message(MSG_DEBUG, "%s: ST_PTR operation failed\n", progname);
+    return -1;
+  }
+  return updi_link_st_ptr_inc16_RSD(pgm, buffer, size >> 1, -1);
+}
diff --git a/updi_readwrite.h b/updi_readwrite.h
new file mode 100644
index 00000000..9519d179
--- /dev/null
+++ b/updi_readwrite.h
@@ -0,0 +1,51 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_readwrite_h
+#define updi_readwrite_h
+
+#include "libavrdude.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int updi_read_cs(PROGRAMMER * pgm, uint8_t address, uint8_t * value);
+int updi_write_cs(PROGRAMMER * pgm, uint8_t address, uint8_t value);
+int updi_write_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
+int updi_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
+int updi_read_byte(PROGRAMMER * pgm, uint32_t address, uint8_t * value);
+int updi_write_byte(PROGRAMMER * pgm, uint32_t address, uint8_t value);
+int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+int updi_write_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+int updi_read_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_readwrite_h */
diff --git a/updi_state.c b/updi_state.c
new file mode 100644
index 00000000..63d80f46
--- /dev/null
+++ b/updi_state.c
@@ -0,0 +1,55 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#include "ac_cfg.h"
+
+#include "libavrdude.h"
+#include "updi_state.h"
+
+updi_sib_info* updi_get_sib_info(PROGRAMMER * pgm)
+{
+  return &((updi_state *)(pgm->cookie))->sib_info;
+}
+
+updi_datalink_mode updi_get_datalink_mode(PROGRAMMER * pgm)
+{
+  return ((updi_state *)(pgm->cookie))->datalink_mode;
+}
+
+void updi_set_datalink_mode(PROGRAMMER * pgm, updi_datalink_mode mode)
+{
+  ((updi_state *)(pgm->cookie))->datalink_mode = mode;
+}
+
+updi_nvm_mode updi_get_nvm_mode(PROGRAMMER * pgm)
+{
+  return ((updi_state *)(pgm->cookie))->nvm_mode;
+}
+
+void updi_set_nvm_mode(PROGRAMMER * pgm, updi_nvm_mode mode)
+{
+  ((updi_state *)(pgm->cookie))->nvm_mode = mode;
+}
diff --git a/updi_state.h b/updi_state.h
new file mode 100644
index 00000000..d4e39d4c
--- /dev/null
+++ b/updi_state.h
@@ -0,0 +1,85 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2021  Dawid Buchwald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* $Id$ */
+
+/*
+ * Based on pymcuprog
+ * See https://github.com/microchip-pic-avr-tools/pymcuprog
+ */
+
+#ifndef updi_state_h
+#define updi_state_h
+
+#include "libavrdude.h"
+
+typedef enum
+{
+  UPDI_LINK_MODE_16BIT,
+  UPDI_LINK_MODE_24BIT
+} updi_datalink_mode;
+
+typedef enum
+{
+  UPDI_NVM_MODE_V0,
+  UPDI_NVM_MODE_V2,
+  UPDI_NVM_MODE_V3
+} updi_nvm_mode;
+
+#define SIB_INFO_STRING_LENGTH 32
+#define SIB_INFO_FAMILY_LENGTH 8
+#define SIB_INFO_NVM_LENGTH    3
+#define SIB_INFO_DEBUG_LENGTH  3
+#define SIB_INFO_PDI_LENGTH    4
+#define SIB_INFO_EXTRA_LENGTH  20
+
+typedef struct 
+{
+  unsigned char sib_string[SIB_INFO_STRING_LENGTH+1];
+  char family_string[SIB_INFO_FAMILY_LENGTH+1];
+  char nvm_string[SIB_INFO_NVM_LENGTH+1];
+  char debug_string[SIB_INFO_DEBUG_LENGTH+1];
+  char pdi_string[SIB_INFO_PDI_LENGTH+1];
+  char extra_string[SIB_INFO_EXTRA_LENGTH+1];
+  char nvm_version;
+  char debug_version;
+} updi_sib_info;
+
+typedef struct
+{
+  updi_sib_info sib_info;
+  updi_datalink_mode datalink_mode;
+  updi_nvm_mode nvm_mode;
+} updi_state;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+updi_sib_info* updi_get_sib_info(PROGRAMMER * pgm);
+updi_datalink_mode updi_get_datalink_mode(PROGRAMMER * pgm);
+void updi_set_datalink_mode(PROGRAMMER * pgm, updi_datalink_mode mode);
+updi_nvm_mode updi_get_nvm_mode(PROGRAMMER * pgm);
+void updi_set_nvm_mode(PROGRAMMER * pgm, updi_nvm_mode mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* updi_state_h */

From 0bb1b758a4640c399cf72d9c5764a6e4db7beda9 Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Tue, 21 Dec 2021 21:30:31 +0100
Subject: [PATCH 11/12] Removed files from old location

---
 avrdude/serialupdi.c     |  664 --------------------
 avrdude/serialupdi.h     |   43 --
 avrdude/updi_constants.h |  156 -----
 avrdude/updi_link.c      |  928 ---------------------------
 avrdude/updi_link.h      |   59 --
 avrdude/updi_nvm.c       | 1275 --------------------------------------
 avrdude/updi_nvm.h       |   51 --
 avrdude/updi_readwrite.c |  316 ----------
 avrdude/updi_readwrite.h |   51 --
 avrdude/updi_state.c     |   55 --
 avrdude/updi_state.h     |   85 ---
 11 files changed, 3683 deletions(-)
 delete mode 100644 avrdude/serialupdi.c
 delete mode 100644 avrdude/serialupdi.h
 delete mode 100644 avrdude/updi_constants.h
 delete mode 100644 avrdude/updi_link.c
 delete mode 100644 avrdude/updi_link.h
 delete mode 100644 avrdude/updi_nvm.c
 delete mode 100644 avrdude/updi_nvm.h
 delete mode 100644 avrdude/updi_readwrite.c
 delete mode 100644 avrdude/updi_readwrite.h
 delete mode 100644 avrdude/updi_state.c
 delete mode 100644 avrdude/updi_state.h

diff --git a/avrdude/serialupdi.c b/avrdude/serialupdi.c
deleted file mode 100644
index 62a9d44c..00000000
--- a/avrdude/serialupdi.c
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Interface to the SerialUPDI programmer.
- *
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-#include "ac_cfg.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "avrdude.h"
-#include "libavrdude.h"
-#include "serialupdi.h"
-#include "updi_link.h"
-#include "updi_state.h"
-#include "updi_readwrite.h"
-#include "updi_nvm.h"
-#include "updi_constants.h"
-
-static int serialupdi_enter_progmode(PROGRAMMER * pgm);
-static int serialupdi_leave_progmode(PROGRAMMER * pgm);
-
-static void serialupdi_setup(PROGRAMMER * pgm)
-{
-  if ((pgm->cookie = malloc(sizeof(updi_state))) == 0) {
-    avrdude_message(MSG_INFO,
-	    "%s: serialupdi_setup(): Out of memory allocating private data\n",
-	    progname);
-    exit(1);
-  }
-  memset(pgm->cookie, 0, sizeof(updi_state));
-  updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
-}
-
-static void serialupdi_teardown(PROGRAMMER * pgm)
-{
-  free(pgm->cookie);
-}
-
-static int serialupdi_open(PROGRAMMER * pgm, char * port)
-{
-  strcpy(pgm->port, port);
-  return updi_link_open(pgm);
-}
-
-static int serialupdi_decode_sib(PROGRAMMER * pgm, updi_sib_info * sib_info)
-{
-  char * str_ptr;
-
-  sib_info->sib_string[SIB_INFO_STRING_LENGTH]=0;
-  avrdude_message(MSG_DEBUG, "%s: Received SIB: [%s]\n", progname, sib_info->sib_string);
-  memset(sib_info->family_string, 0, SIB_INFO_FAMILY_LENGTH+1);
-  memset(sib_info->nvm_string, 0, SIB_INFO_NVM_LENGTH+1);
-  memset(sib_info->debug_string, 0, SIB_INFO_DEBUG_LENGTH+1);
-  memset(sib_info->pdi_string, 0, SIB_INFO_PDI_LENGTH+1);
-  memset(sib_info->pdi_string, 0, SIB_INFO_PDI_LENGTH+1);
-  memset(sib_info->extra_string, 0, SIB_INFO_EXTRA_LENGTH+1);
-
-  memcpy(sib_info->family_string, sib_info->sib_string, SIB_INFO_FAMILY_LENGTH);
-  memcpy(sib_info->nvm_string, sib_info->sib_string + 8, SIB_INFO_NVM_LENGTH);
-  memcpy(sib_info->debug_string, sib_info->sib_string + 11, SIB_INFO_DEBUG_LENGTH);
-  memcpy(sib_info->pdi_string, sib_info->sib_string + 15, SIB_INFO_PDI_LENGTH);
-  strcpy(sib_info->extra_string, (char *)sib_info->sib_string + 19);
-
-  str_ptr = strstr(sib_info->nvm_string, ":");
-  if (!str_ptr) {
-    avrdude_message(MSG_INFO, "%s: Incorrect format of NVM string\n", progname);
-    return -1;
-  }
-  sib_info->nvm_version = *(str_ptr+1);
-
-  str_ptr = strstr(sib_info->debug_string, ":");
-  if (!str_ptr) {
-    avrdude_message(MSG_INFO, "%s: Incorrect format of DEBUG string\n", progname);
-    return -1;
-  }
-  sib_info->debug_version = *(str_ptr+1);
-
-  avrdude_message(MSG_DEBUG, "%s: Device family ID: %s\n", progname, sib_info->family_string);
-  avrdude_message(MSG_DEBUG, "%s: NVM interface: %s\n", progname, sib_info->nvm_string);
-  avrdude_message(MSG_DEBUG, "%s: Debug interface: %s\n", progname, sib_info->debug_string);
-  avrdude_message(MSG_DEBUG, "%s: PDI oscillator: %s\n", progname, sib_info->pdi_string);
-  avrdude_message(MSG_DEBUG, "%s: Extra information: %s\n", progname, sib_info->extra_string);
-  switch (sib_info->nvm_version) {
-    case '0':
-      avrdude_message(MSG_INFO, "%s: NVM type 0: 16-bit, page oriented write\n", progname);
-      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V0);
-      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
-      break;
-    case '2':
-      avrdude_message(MSG_INFO, "%s: NVM type 2: 24-bit, word oriented write\n", progname);
-      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V2);
-      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_24BIT);
-      break;
-    case '3':
-      avrdude_message(MSG_INFO, "%s: NVM type 3: 16-bit, page oriented\n", progname);
-      updi_set_nvm_mode(pgm, UPDI_NVM_MODE_V3);
-      updi_set_datalink_mode(pgm, UPDI_LINK_MODE_16BIT);
-      break;
-    default:
-      avrdude_message(MSG_INFO, "%s: Unsupported NVM type: %c, please update software\n", progname, sib_info->nvm_version);
-      return -1;
-  }
-  return 0;
-}
-
-static void serialupdi_close(PROGRAMMER * pgm)
-{
-  if (serialupdi_leave_progmode(pgm) < 0) {
-    avrdude_message(MSG_INFO, "%s: Unable to leave NVM programming mode\n", progname);
-  }
-  updi_link_close(pgm);
-}
-
-typedef enum {
-  APPLY_RESET,
-  RELEASE_RESET
-} reset_mode;
-
-static int serialupdi_reset(PROGRAMMER * pgm, reset_mode mode)
-{
-/*
-    def reset(self, apply_reset):
-        """
-        Applies or releases an UPDI reset condition
-
-        :param apply_reset: True to apply, False to release
-        """
-        if apply_reset:
-            self.logger.info("Apply reset")
-            self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, constants.UPDI_RESET_REQ_VALUE)
-        else:
-            self.logger.info("Release reset")
-            self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, 0x00)
-*/
-  switch (mode) {
-    case APPLY_RESET:
-      avrdude_message(MSG_DEBUG, "%s: Sending reset request\n", progname);
-      return updi_write_cs(pgm, UPDI_ASI_RESET_REQ, UPDI_RESET_REQ_VALUE);
-    case RELEASE_RESET:
-      avrdude_message(MSG_DEBUG, "%s: Sending release reset request\n", progname);
-      return updi_write_cs(pgm, UPDI_ASI_RESET_REQ, 0x00);
-  }
-  return -1;
-}
-
-static int serialupdi_wait_for_unlock(PROGRAMMER * pgm, unsigned int ms) {
-/*
-    def wait_unlocked(self, timeout_ms):
-        """
-        Waits for the device to be unlocked.
-        All devices boot up as locked until proven otherwise
-
-        :param timeout_ms: number of milliseconds to wait
-        """
-        timeout = Timeout(timeout_ms)
-
-        while not timeout.expired():
-            if not self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & (
-                    1 << constants.UPDI_ASI_SYS_STATUS_LOCKSTATUS):
-                return True
-
-        self.logger.error("Timeout waiting for device to unlock")
-        return False
-*/  
-  unsigned long start_time;
-  unsigned long current_time;
-  struct timeval tv;
-  uint8_t status;
-  gettimeofday (&tv, NULL);
-  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
-  do {
-    if (updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &status) >= 0) {
-      if (!(status & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS))) {
-        return 0;
-      }
-    }
-    gettimeofday (&tv, NULL);
-    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
-  } while ((current_time - start_time) < (ms * 1000));
-
-  avrdude_message(MSG_INFO, "%s: Timeout waiting for device to unlock\n", progname);
-  return -1;
-}
-
-static int serialupdi_in_prog_mode(PROGRAMMER * pgm, uint8_t * in_prog_mode)
-{
-/*
-    def in_prog_mode(self):
-        """
-        Checks whether the NVM PROG flag is up
-        """
-        if self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & (1 << constants.UPDI_ASI_SYS_STATUS_NVMPROG):
-            return True
-        return False
-*/
-  uint8_t value;
-  int rc;
-  
-  rc = updi_read_cs(pgm, UPDI_ASI_SYS_STATUS, &value);
-  
-  if (rc < 0) {
-    avrdude_message(MSG_INFO, "%s: Read CS operation failed\n", progname);
-    return rc;
-  }
-
-  if (value & (1 << UPDI_ASI_SYS_STATUS_NVMPROG)) {
-    *in_prog_mode = 1;
-  } else {
-    *in_prog_mode = 0;
-  }
-  return 0;
-}
-
-static int serialupdi_unlock(PROGRAMMER * pgm, AVRPART * p);
-
-static int serialupdi_enter_progmode(PROGRAMMER * pgm)
-{
-/*
-def enter_progmode(self):
-        """
-        Enters into NVM programming mode
-        """
-        # First check if NVM is already enabled
-        if self.in_prog_mode():
-            self.logger.info("Already in NVM programming mode")
-            return True
-
-        self.logger.info("Entering NVM programming mode")
-
-        # Put in the key
-        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_NVM)
-
-        # Check key status
-        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
-        self.logger.debug("Key status = 0x%02X", key_status)
-
-        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_NVMPROG):
-            self.logger.error("Key status = 0x%02X", key_status)
-            raise IOError("Key not accepted")
-
-        # Toggle reset
-        self.reset(apply_reset=True)
-        self.reset(apply_reset=False)
-
-        # And wait for unlock
-        if not self.wait_unlocked(100):
-            raise IOError("Failed to enter NVM programming mode: device is locked")
-
-        # Check for NVMPROG flag
-        if not self.in_prog_mode():
-            raise IOError("Failed to enter NVM programming mode")
-
-        self.logger.debug("Now in NVM programming mode")
-        return True
-*/
-  uint8_t in_prog_mode;
-  unsigned char buffer[8];
-  uint8_t key_status;
-
-  if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
-    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed, attempting reset\n", progname);
-    if (serialupdi_leave_progmode(pgm) < 0) {
-      avrdude_message(MSG_INFO, "%s: Unable to leave progmode\n", progname);
-      return -1;
-    }
-    if (updi_link_init(pgm) < 0) {
-      avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
-      return -1;
-    }
-    avrdude_message(MSG_INFO, "%s: UPDI link initialization OK\n", progname);
-    if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
-      avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed again, exiting\n", progname);
-      return -1;
-    }
-  }
-  if (in_prog_mode) {
-    avrdude_message(MSG_DEBUG, "%s: Already in prog mode\n", progname);
-    return 0;
-  }
-  avrdude_message(MSG_INFO, "%s: Entering NVM programming mode\n", progname);
-  
-  memcpy(buffer, UPDI_KEY_NVM, sizeof(buffer));
-  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
-    avrdude_message(MSG_INFO, "%s: Writing NVM KEY failed\n", progname);
-    return -1;
-  }
-
-  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
-    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
-
-  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_NVMPROG))) {
-    avrdude_message(MSG_INFO, "%s: Key was not accepted\n", progname);
-    return -1;
-  }
-
-  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
-    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
-    return -1;
-  }
-
-  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
-    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
-    return -1;
-  }
-
-  if (serialupdi_wait_for_unlock(pgm, 100) < 0) {
-    avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode: device is locked\n", progname);
-    if (!ovsigck) {
-      return -1;
-    }
-    if (serialupdi_unlock(pgm, 0x00) < 0) {
-      return -1;
-    }
-    if (updi_link_init(pgm) < 0) {
-      return -1;
-    }
-    return serialupdi_enter_progmode(pgm);
-  }
-
-  if (serialupdi_in_prog_mode(pgm, &in_prog_mode) < 0) {
-    avrdude_message(MSG_INFO, "%s: Checking UPDI NVM prog mode failed\n", progname);
-    return -1;
-  }
-
-  if (!in_prog_mode) {
-    avrdude_message(MSG_INFO, "%s: Failed to enter NVM programming mode\n", progname);
-    return -1;
-  }
-
-  avrdude_message(MSG_DEBUG, "%s: Entered NVM programming mode\n", progname);
-
-  updi_sib_info * sib_info = updi_get_sib_info(pgm);
-
-  if (updi_read_sib(pgm, sib_info->sib_string, 32) < 0) {
-    avrdude_message(MSG_INFO, "%s: Read SIB operation failed\n", progname);
-    return -1;
-  }
-  if (serialupdi_decode_sib(pgm, sib_info) < 0) {
-    avrdude_message(MSG_INFO, "%s: Decode SIB_INFO failed\n", progname);
-    return -1;
-  }
-
-  return 0;
-}
-
-static int serialupdi_leave_progmode(PROGRAMMER * pgm)
-{
-/*
-    def leave_progmode(self):
-        """
-        Disables UPDI which releases any keys enabled
-        """
-        self.logger.info("Leaving NVM programming mode")
-        self.reset(apply_reset=True)
-        self.reset(apply_reset=False)
-        self.readwrite.write_cs(constants.UPDI_CS_CTRLB,
-                                (1 << constants.UPDI_CTRLB_UPDIDIS_BIT) | (1 << constants.UPDI_CTRLB_CCDETDIS_BIT))
-*/
-  avrdude_message(MSG_INFO, "%s: Leaving NVM programming mode\n", progname);
-
-  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
-    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
-    return -1;
-  }
-
-  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
-    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
-    return -1;
-  }
-
-  return updi_write_cs(pgm, UPDI_CS_CTRLB, (1 << UPDI_CTRLB_UPDIDIS_BIT) | (1 << UPDI_CTRLB_CCDETDIS_BIT));
-}
-
-static int serialupdi_initialize(PROGRAMMER * pgm, AVRPART * p)
-{
-  if (updi_link_init(pgm) < 0) {
-    avrdude_message(MSG_INFO, "%s: UPDI link initialization failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_INFO, "%s: UPDI link initialization OK\n", progname);
-  if (serialupdi_enter_progmode(pgm) < 0) {
-    avrdude_message(MSG_INFO, "%s: Unable to enter NVM programming mode\n", progname);
-    return -1;
-  }
-
-  return 0;
-}
-
-static void serialupdi_disable(PROGRAMMER * pgm)
-{
-  /* Do nothing. */
-
-  return;
-}
-
-static void serialupdi_enable(PROGRAMMER * pgm)
-{
-  /* Do nothing. */
-
-  return;
-}
-
-static void serialupdi_display(PROGRAMMER * pgm, const char * p)
-{
-  return;
-}
-
-static int serialupdi_cmd(PROGRAMMER * pgm, const unsigned char * cmd,
-                          unsigned char * res)
-{
-  avrdude_message(MSG_INFO, "%s: error: cmd %s[%s] not implemented yet\n",
-    	    progname, cmd, res);
-  return -1;
-}
-
-static int serialupdi_program_enable(PROGRAMMER * pgm, AVRPART * p)
-{
-  avrdude_message(MSG_INFO, "%s: error: program enable not implemented yet\n",
-    	    progname);
-  return -1;
-}
-
-static int serialupdi_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, 
-                                unsigned long addr, unsigned char * value)
-{
-  return updi_read_byte(pgm, mem->offset + addr, value);
-}
-
-static int serialupdi_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
-                                 unsigned long addr, unsigned char value)
-{
-  if (strstr(mem->desc, "fuse") != 0) {
-    return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
-  }
-  if (strcmp(mem->desc, "lock") == 0) {
-    return updi_nvm_write_fuse(pgm, p, mem->offset + addr, value);
-  }
-  if (strcmp(mem->desc, "eeprom") == 0) {
-    unsigned char buffer[1];
-    buffer[0]=value;
-    return updi_nvm_write_eeprom(pgm, p, mem->offset + addr, buffer, 1);
-  }
-  if (strcmp(mem->desc, "flash") == 0) {
-    unsigned char buffer[1];
-    buffer[0]=value;
-    return updi_nvm_write_flash(pgm, p, mem->offset + addr, buffer, 1);
-  }
-  return updi_write_byte(pgm, mem->offset + addr, value);
-}
-
-
-static int serialupdi_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
-                                 unsigned int page_size,
-                                 unsigned int addr, unsigned int n_bytes)
-{
-  if (n_bytes > m->readsize) {
-    unsigned int read_offset = addr;
-    unsigned int remaining_bytes = n_bytes;
-    int read_bytes = 0;
-    int rc;
-    while (remaining_bytes > 0) {
-      rc = updi_read_data(pgm, m->offset + read_offset, m->buf + read_offset, 
-                          remaining_bytes > m->readsize ? m->readsize : remaining_bytes);
-      if (rc < 0) {
-        avrdude_message(MSG_INFO, "%s: Paged load operation failed\n", progname);
-        return rc;
-      } else {
-        read_bytes+=rc;
-        read_offset+=m->readsize;
-        remaining_bytes-=m->readsize;
-      }
-    }
-    return read_bytes;
-  } else {
-    return updi_read_data(pgm, m->offset + addr, m->buf + addr, n_bytes);
-  }
-}
-
-static int serialupdi_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
-                                  unsigned int page_size,
-                                  unsigned int addr, unsigned int n_bytes)
-{
-  int rc;
-  if (n_bytes > m->page_size) {
-    unsigned int write_offset = addr;
-    unsigned int remaining_bytes = n_bytes;
-    int write_bytes = 0;
-    while (remaining_bytes > 0) {
-
-      if (strcmp(m->desc, "eeprom")==0) {
-        rc = updi_nvm_write_eeprom(pgm, p, m->offset + write_offset, m->buf + write_offset, 
-                                   remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
-      } else if (strcmp(m->desc, "flash")==0) {
-        rc = updi_nvm_write_flash(pgm, p, m->offset + write_offset, m->buf + write_offset, 
-                                  remaining_bytes > m->page_size ? m->page_size : remaining_bytes);
-      } else {
-        avrdude_message(MSG_INFO, "%s: Invalid memory type: <%s:%d>, 0x%06X, %d (0x%04X)\n", progname, m->desc, page_size, addr, n_bytes, n_bytes);
-        rc = -1;
-      }
-
-      if (rc < 0) {
-        avrdude_message(MSG_INFO, "%s: Paged write operation failed\n", progname);
-        return rc;
-      } else {
-        write_bytes+=rc;
-        write_offset+=m->page_size;
-        remaining_bytes-=m->page_size;
-      }
-    }
-    return write_bytes;
-  } else {
-    if (strcmp(m->desc, "eeprom")==0) {
-      rc = updi_nvm_write_eeprom(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
-    } else if (strcmp(m->desc, "flash")==0) {
-      rc = updi_nvm_write_flash(pgm, p, m->offset+addr, m->buf+addr, n_bytes);
-    } else {
-      avrdude_message(MSG_INFO, "%s: Invalid memory type: <%s:%d>, 0x%06X, %d (0x%04X)\n", progname, m->desc, page_size, addr, n_bytes, n_bytes);
-      rc = -1;
-    }
-    return rc;
-  }
-}
-
-static int serialupdi_unlock(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def unlock(self):
-        """
-        Unlock by chip erase
-        """
-        # Put in the key
-        self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE)
-
-        # Check key status
-        key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS)
-        self.logger.debug("Key status = 0x%02X", key_status)
-
-        if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE):
-            raise PymcuprogError("Key not accepted")
-
-        # Toggle reset
-        self.reset(apply_reset=True)
-        self.reset(apply_reset=False)
-
-        # And wait for unlock
-        if not self.wait_unlocked(500):
-            raise PymcuprogError("Failed to chip erase using key")
-*/
-  unsigned char buffer[8];
-  uint8_t key_status;
-  
-  memcpy(buffer, UPDI_KEY_CHIPERASE, sizeof(buffer));
-
-  if (updi_write_key(pgm, buffer, UPDI_KEY_64, sizeof(buffer)) < 0) {
-    avrdude_message(MSG_INFO, "%s: Writing NVM KEY failed\n", progname);
-    return -1;
-  }
-
-  if (updi_read_cs(pgm, UPDI_ASI_KEY_STATUS, &key_status) < 0) {
-    avrdude_message(MSG_INFO, "%s: Checking KEY status failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Key status: 0x%02X\n", progname, key_status);
-
-  if (!(key_status & (1 << UPDI_ASI_KEY_STATUS_CHIPERASE))) {
-    avrdude_message(MSG_INFO, "%s: Key not accepted\n", progname);
-    return -1;
-  }
-
-  if (serialupdi_reset(pgm, APPLY_RESET) < 0) {
-    avrdude_message(MSG_INFO, "%s: Apply reset operation failed\n", progname);
-    return -1;
-  }
-
-  if (serialupdi_reset(pgm, RELEASE_RESET) < 0) {
-    avrdude_message(MSG_INFO, "%s: Release reset operation failed\n", progname);
-    return -1;
-  }
-
-  return serialupdi_wait_for_unlock(pgm, 500);
-}
-
-static int serialupdi_chip_erase(PROGRAMMER * pgm, AVRPART * p)
-{
-  if (updi_nvm_chip_erase(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Chip erase failed, device might be locked, attempting unlock now\n", progname);
-    return serialupdi_unlock(pgm, p);
-  }
-  return 0;
-}
-
-static int serialupdi_page_erase(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
-                                 unsigned int baseaddr)
-{
-  avrdude_message(MSG_INFO, "%s: error: page erase not implemented yet\n",
-    	    progname);
-  return -1;
-}
-
-
-void serialupdi_initpgm(PROGRAMMER * pgm)
-{
-  strcpy(pgm->type, "serialupdi");
-
-  /*
-   * mandatory functions
-   */
-
-  pgm->initialize     = serialupdi_initialize;
-  pgm->display        = serialupdi_display;
-  pgm->enable         = serialupdi_enable;
-  pgm->disable        = serialupdi_disable;
-  pgm->program_enable = serialupdi_program_enable;
-  pgm->chip_erase     = serialupdi_chip_erase;
-  pgm->cmd            = serialupdi_cmd;
-  pgm->open           = serialupdi_open;
-  pgm->close          = serialupdi_close;
-  pgm->read_byte      = serialupdi_read_byte;
-  pgm->write_byte     = serialupdi_write_byte;
-
-  /*
-   * optional functions
-   */
-
-  pgm->unlock         = serialupdi_unlock;
-  pgm->paged_write    = serialupdi_paged_write;
-  pgm->paged_load     = serialupdi_paged_load;
-  pgm->page_erase     = serialupdi_page_erase;
-  pgm->setup          = serialupdi_setup;
-  pgm->teardown       = serialupdi_teardown;
-
-}
-
-const char serialupdi_desc[] = "Driver for SerialUPDI programmers";
diff --git a/avrdude/serialupdi.h b/avrdude/serialupdi.h
deleted file mode 100644
index ff7270d5..00000000
--- a/avrdude/serialupdi.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#ifndef serialupdi_h
-#define serialupdi_h
-
-#include "libavrdude.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern const char serialupdi_desc[];
-void serialupdi_initpgm (PROGRAMMER * pgm);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* serialupdi_h */
diff --git a/avrdude/updi_constants.h b/avrdude/updi_constants.h
deleted file mode 100644
index ff8a446f..00000000
--- a/avrdude/updi_constants.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#ifndef updi_constants_h
-#define updi_constants_h
-
-#define UPDI_BREAK       0x00
-
-#define UPDI_LDS         0x00
-#define UPDI_STS         0x40
-#define UPDI_LD          0x20
-#define UPDI_ST          0x60
-#define UPDI_LDCS        0x80
-#define UPDI_STCS        0xC0
-#define UPDI_REPEAT      0xA0
-#define UPDI_KEY         0xE0
-
-#define UPDI_PTR         0x00
-#define UPDI_PTR_INC     0x04
-#define UPDI_PTR_ADDRESS 0x08
-
-#define UPDI_ADDRESS_8   0x00
-#define UPDI_ADDRESS_16  0x04
-#define UPDI_ADDRESS_24  0x08
-
-#define UPDI_DATA_8      0x00
-#define UPDI_DATA_16     0x01
-#define UPDI_DATA_24     0x02
-
-#define UPDI_KEY_SIB     0x04
-#define UPDI_KEY_KEY     0x00
-
-#define UPDI_KEY_64      0x00
-#define UPDI_KEY_128     0x01
-#define UPDI_KEY_256     0x02
-
-#define UPDI_SIB_8BYTES  UPDI_KEY_64
-#define UPDI_SIB_16BYTES UPDI_KEY_128
-#define UPDI_SIB_32BYTES UPDI_KEY_256
-
-#define UPDI_REPEAT_BYTE 0x00
-#define UPDI_REPEAT_WORD 0x01
-
-#define UPDI_PHY_SYNC    0x55
-#define UPDI_PHY_ACK     0x40
-
-#define UPDI_MAX_REPEAT_SIZE (0xFF+1) // Repeat counter of 1-byte, with off-by-one counting
-
-//# CS and ASI Register Address map
-#define UPDI_CS_STATUSA     0x00
-#define UPDI_CS_STATUSB     0x01
-#define UPDI_CS_CTRLA       0x02
-#define UPDI_CS_CTRLB       0x03
-#define UPDI_ASI_KEY_STATUS 0x07
-#define UPDI_ASI_RESET_REQ  0x08
-#define UPDI_ASI_CTRLA      0x09
-#define UPDI_ASI_SYS_CTRLA  0x0A
-#define UPDI_ASI_SYS_STATUS 0x0B
-#define UPDI_ASI_CRC_STATUS 0x0C
-
-#define UPDI_CTRLA_IBDLY_BIT    7
-#define UPDI_CTRLB_CCDETDIS_BIT 3
-#define UPDI_CTRLB_UPDIDIS_BIT  2
-
-#define UPDI_KEY_NVM       "NVMProg "
-#define UPDI_KEY_CHIPERASE "NVMErase"
-#define UPDI_KEY_UROW      "NVMUs&te"
-
-#define UPDI_ASI_STATUSA_REVID 4
-#define UPDI_ASI_STATUSB_PESIG 0
-
-#define UPDI_ASI_KEY_STATUS_CHIPERASE  3
-#define UPDI_ASI_KEY_STATUS_NVMPROG    4
-#define UPDI_ASI_KEY_STATUS_UROWWRITE  5
-
-#define UPDI_ASI_SYS_STATUS_RSTSYS     5
-#define UPDI_ASI_SYS_STATUS_INSLEEP    4
-#define UPDI_ASI_SYS_STATUS_NVMPROG    3
-#define UPDI_ASI_SYS_STATUS_UROWPROG   2
-#define UPDI_ASI_SYS_STATUS_LOCKSTATUS 0
-
-#define UPDI_ASI_SYS_CTRLA_UROW_FINAL  1
-
-#define UPDI_RESET_REQ_VALUE  0x59
-
-// FLASH CONTROLLER
-#define UPDI_NVMCTRL_CTRLA    0x00
-#define UPDI_NVMCTRL_CTRLB    0x01
-#define UPDI_NVMCTRL_STATUS   0x02
-#define UPDI_NVMCTRL_INTCTRL  0x03
-#define UPDI_NVMCTRL_INTFLAGS 0x04
-#define UPDI_NVMCTRL_DATAL    0x06
-#define UPDI_NVMCTRL_DATAH    0x07
-#define UPDI_NVMCTRL_ADDRL    0x08
-#define UPDI_NVMCTRL_ADDRH    0x09
-
-// NVMCTRL v0 CTRLA
-#define UPDI_V0_NVMCTRL_CTRLA_NOP              0x00
-#define UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE       0x01
-#define UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE       0x02
-#define UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE 0x03
-#define UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR  0x04
-#define UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE       0x05
-#define UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM     0x06
-#define UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE       0x07
-
-// NVMCTRL v2 CTRLA
-#define UPDI_V2_NVMCTRL_CTRLA_NOCMD              0x00
-#define UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE        0x02
-#define UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE   0x08
-#define UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE 0x13
-#define UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE         0x20
-#define UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE       0x30
-
-// NVMCTRL v3 CTRLA
-#define UPDI_V3_NVMCTRL_CTRLA_NOCMD                    0x00
-#define UPDI_V3_NVMCTRL_CTRLA_NOP                      0x01
-#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE         0x04
-#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE_WRITE   0x05
-#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE         0x08
-#define UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR  0x0F
-#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_WRITE        0x14
-#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE  0x15
-#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE        0x17
-#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_BUFFER_CLEAR 0x1F
-#define UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE               0x20
-#define UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE             0x30
-
-#define UPDI_NVM_STATUS_WRITE_ERROR 2
-#define UPDI_NVM_STATUS_EEPROM_BUSY 1
-#define UPDI_NVM_STATUS_FLASH_BUSY  0
-
-#endif /* updi_constants_h */
diff --git a/avrdude/updi_link.c b/avrdude/updi_link.c
deleted file mode 100644
index a3852bfe..00000000
--- a/avrdude/updi_link.c
+++ /dev/null
@@ -1,928 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#include "ac_cfg.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "avrdude.h"
-#include "libavrdude.h"
-#include "updi_link.h"
-#include "updi_constants.h"
-#include "updi_state.h"
-
-#include <sys/time.h>
-
-void msleep(int tms)
-{
-    struct timeval tv;
-    tv.tv_sec  = tms / 1000;
-    tv.tv_usec = (tms % 1000) * 1000;
-    select (0, NULL, NULL, NULL, &tv);
-}
-
-static int updi_physical_open(PROGRAMMER* pgm, int baudrate, unsigned long cflags)
-{
-  serial_recv_timeout = 100;
-  union pinfo pinfo;
-
-  pinfo.serialinfo.baud = baudrate;
-  pinfo.serialinfo.cflags = cflags;
-
-  avrdude_message(MSG_DEBUG, "%s: Opening serial port...\n", progname);
-
-  if (serial_open(pgm->port, pinfo, &pgm->fd)==-1) {
-
-    avrdude_message(MSG_INFO, "%s: Serial port open failed!\n", progname);
-    return -1;
-  }
-
-  /*
-   * drain any extraneous input
-   */
-  serial_drain(&pgm->fd, 0);
-
-  return 0;
-}
-
-static void updi_physical_close(PROGRAMMER* pgm)
-{
-  serial_close(&pgm->fd);
-  pgm->fd.ifd = -1;
-}
-
-static int updi_physical_send(PROGRAMMER * pgm, unsigned char * buf, size_t len)
-{
-  size_t i;
-  int rv;
-
-  avrdude_message(MSG_DEBUG, "%s: Sending %lu bytes [", progname, len);
-  for (i=0; i<len; i++) {
-    avrdude_message(MSG_DEBUG, "0x%02x", buf[i]);
-    if (i<len-1) {
-      avrdude_message(MSG_DEBUG, ", ");
-    }
-  }
-  avrdude_message(MSG_DEBUG, "]\n");
-
-  rv = serial_send(&pgm->fd, buf, len);
-  serial_recv(&pgm->fd, buf, len);
-  return rv;
-}
-
-static int updi_physical_recv(PROGRAMMER * pgm, unsigned char * buf, size_t len)
-{
-  size_t i;
-  int rv;
-
-  rv = serial_recv(&pgm->fd, buf, len);
-  if (rv < 0) {
-    avrdude_message(MSG_DEBUG,
-      "%s: serialupdi_recv(): programmer is not responding\n",
-      progname);
-    return -1;
-  }
-
-  avrdude_message(MSG_DEBUG, "%s: Received %lu bytes [", progname, len);
-  for (i=0; i<len; i++) {
-    avrdude_message(MSG_DEBUG, "0x%02x", buf[i]);
-    if (i<len-1) {
-      avrdude_message(MSG_DEBUG, ", ");
-    }
-  }
-  avrdude_message(MSG_DEBUG, "]\n");
-
-  return len;
-}
-
-static int updi_physical_send_double_break(PROGRAMMER * pgm)
-{
-  unsigned char buffer[1];
-
-  avrdude_message(MSG_DEBUG, "%s: Sending double break\n", progname);
-
-  updi_physical_close(pgm);
-
-  if (updi_physical_open(pgm, 300, SERIAL_8E1)==-1) {
-
-    return -1;
-  }
-
-  buffer[0] = UPDI_BREAK;
-
-  serial_send(&pgm->fd, buffer, 1);
-  serial_recv(&pgm->fd, buffer, 1);
-
-  msleep(100);
-
-  buffer[0] = UPDI_BREAK;
-
-  serial_send(&pgm->fd, buffer, 1);
-  serial_recv(&pgm->fd, buffer, 1);
-
-  updi_physical_close(pgm);
-
-  return updi_physical_open(pgm, pgm->baudrate? pgm->baudrate: 115200, SERIAL_8E2);
-}
-
-int updi_physical_sib(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size)
-{
-/*
-    def sib(self):
-        """
-        System information block is just a string coming back from a SIB command
-        """
-        self.send([
-            constants.UPDI_PHY_SYNC,
-            constants.UPDI_KEY | constants.UPDI_KEY_SIB | constants.UPDI_SIB_32BYTES])
-        return self.ser.readline()
-*/
-  unsigned char send_buffer[2];
-
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_KEY | UPDI_KEY_SIB | UPDI_SIB_32BYTES;
-
-  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
-    avrdude_message(MSG_INFO, "%s: SIB request send failed\n", progname);
-    return -1;
-  }
-
-  return updi_physical_recv(pgm, buffer, size);
-}
-
-int updi_link_open(PROGRAMMER * pgm) 
-{
-  return updi_physical_open(pgm, pgm->baudrate? pgm->baudrate: 115200, 
-                            (SERIAL_CS8 | SERIAL_CSTOPB | SERIAL_CREAD | SERIAL_PARENB | SERIAL_CLOCAL));
-}
-
-void updi_link_close(PROGRAMMER * pgm)
-{
-  updi_physical_close(pgm);
-}
-
-static int updi_link_init_session_parameters(PROGRAMMER * pgm) 
-{
-/*
-    def _init_session_parameters(self):
-        """
-        Set the inter-byte delay bit and disable collision detection
-        """
-        self.stcs(constants.UPDI_CS_CTRLB, 1 << constants.UPDI_CTRLB_CCDETDIS_BIT)
-        self.stcs(constants.UPDI_CS_CTRLA, 1 << constants.UPDI_CTRLA_IBDLY_BIT)
-*/
-  if (updi_link_stcs(pgm, UPDI_CS_CTRLB, 1 << UPDI_CTRLB_CCDETDIS_BIT) < 0) {
-    return -1;
-  }
-
-  if (updi_link_stcs(pgm, UPDI_CS_CTRLA, 1 << UPDI_CTRLA_IBDLY_BIT) < 0) {
-    return -1;
-  }
-
-  return 0;
-}
-
-static int updi_link_check(PROGRAMMER * pgm)
-{
-/*
-    def _check_datalink(self):
-        """
-        Check UPDI by loading CS STATUSA
-        """
-        try:
-            if self.ldcs(constants.UPDI_CS_STATUSA) != 0:
-                self.logger.info("UPDI init OK")
-                return True
-        except PymcuprogError:
-            self.logger.warning("Check failed")
-            return False
-        self.logger.info("UPDI not OK - reinitialisation required")
-        return False
-*/
-  int result;
-  uint8_t value;
-  result = updi_link_ldcs(pgm, UPDI_CS_STATUSA, &value);
-  if (result < 0) {
-    avrdude_message(MSG_DEBUG, "%s: Check failed\n", progname);
-    return -1;
-  } else {
-    if (value > 0) {
-      avrdude_message(MSG_DEBUG, "%s: UDPI init OK\n", progname);
-      return 0;
-    } else {
-      avrdude_message(MSG_DEBUG, "%s: UDPI not OK - reinitialisation required\n", progname);
-      return -1;
-    }
-  }
-}
-
-
-int updi_link_init(PROGRAMMER * pgm)
-{
-/*
-    def init_datalink(self):
-        """
-        Init DL layer
-        """
-        self._init_session_parameters()
-        # Check
-        if not self._check_datalink():
-            # Send double break if all is not well, and re-check
-            self.updi_phy.send_double_break()
-            self._init_session_parameters()
-            if not self._check_datalink():
-                raise PymcuprogError("UPDI initialisation failed")
-*/
-  if (updi_link_init_session_parameters(pgm) < 0) {
-    avrdude_message(MSG_INFO, "%s: Session initialisation failed\n", progname);
-    return -1;
-  }
-
-  if (updi_link_check(pgm) < 0) {
-    avrdude_message(MSG_DEBUG, "%s: Datalink not active, resetting...\n", progname);
-    if (updi_physical_send_double_break(pgm) < 0) {
-      avrdude_message(MSG_INFO, "%s: Datalink initialisation failed\n", progname);
-      return -1;
-    }
-    if (updi_link_init_session_parameters(pgm) < 0) {
-      avrdude_message(MSG_INFO, "%s: Session initialisation failed\n", progname);
-      return -1;
-    }
-    if (updi_link_check(pgm) < 0) {
-      avrdude_message(MSG_INFO, "%s: Restoring datalink failed\n", progname);
-      return -1;
-    }
-  }
-  return 0;
-}
-
-int updi_link_ldcs(PROGRAMMER * pgm, uint8_t address, uint8_t * value) 
-{
-/*
-    def ldcs(self, address):
-        """
-        Load data from Control/Status space
-
-        :param address: address to load
-        """
-        self.logger.debug("LDCS from 0x%02X", address)
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)])
-        response = self.updi_phy.receive(self.LDCS_RESPONSE_BYTES)
-        numbytes_received = len(response)
-        if numbytes_received != self.LDCS_RESPONSE_BYTES:
-            raise PymcuprogError("Unexpected number of bytes in response: "
-                                 "{} byte(s) expected {} byte(s)".format(numbytes_received, self.LDCS_RESPONSE_BYTES))
-
-        return response[0]
-*/
-  unsigned char buffer[2];
-  int result;
-  avrdude_message(MSG_DEBUG, "%s: LDCS from 0x%02X\n", progname, address);
-  buffer[0]=UPDI_PHY_SYNC;
-  buffer[1]=UPDI_LDCS | (address & 0x0F);
-  if (updi_physical_send(pgm, buffer, 2) < 0) {
-    avrdude_message(MSG_INFO, "%s: LDCS send operation failed\n", progname);
-    return -1;
-  }
-  result = updi_physical_recv(pgm, buffer, 1);
-  if (result != 1) {
-    if (result >= 0) {
-      avrdude_message(MSG_INFO, "%s: Incorrect response size, received %d instead of %d bytes\n", progname, result, 1);
-    }
-    return -1;
-  }
-  * value = buffer[0];
-  return 0;
-}
-
-int updi_link_stcs(PROGRAMMER * pgm, uint8_t address, uint8_t value)
-{
-/*
-    def stcs(self, address, value):
-        """
-        Store a value to Control/Status space
-
-        :param address: address to store to
-        :param value: value to write
-        """
-        self.logger.debug("STCS to 0x%02X", address)
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_STCS | (address & 0x0F), value])
-*/
-  unsigned char buffer[3];
-  avrdude_message(MSG_DEBUG, "%s: STCS 0x%02X to address 0x%02X\n", progname, value, address);
-  buffer[0] = UPDI_PHY_SYNC;
-  buffer[1] = UPDI_STCS | (address & 0x0F);
-  buffer[2] = value;
-  return updi_physical_send(pgm, buffer, 3);
-}
-
-int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
-{
-/*
-    def ld_ptr_inc(self, size):
-        """
-        Loads a number of bytes from the pointer location with pointer post-increment
- 
-        :param size: number of bytes to load
-        :return: values read
-        """
-        self.logger.debug("LD8 from ptr++")
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC |
-                            constants.UPDI_DATA_8])
-        return self.updi_phy.receive(size)
-*/
-  unsigned char send_buffer[2];
-  avrdude_message(MSG_DEBUG, "%s: LD8 from ptr++\n", progname);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_LD | UPDI_PTR_INC | UPDI_DATA_8;
-  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
-    avrdude_message(MSG_INFO, "%s: LD_PTR_INC send operation failed\n", progname);
-    return -1;
-  }
-  return updi_physical_recv(pgm, buffer, size);
-}
-
-int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words)
-{
-/*
-    def ld_ptr_inc16(self, words):
-        """
-        Load a 16-bit word value from the pointer location with pointer post-increment
-
-        :param words: number of words to load
-        :return: values read
-        """
-        self.logger.debug("LD16 from ptr++")
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC |
-                            constants.UPDI_DATA_16])
-        return self.updi_phy.receive(words << 1)
-*/
-  unsigned char send_buffer[2];
-  avrdude_message(MSG_DEBUG, "%s: LD16 from ptr++\n", progname);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_LD | UPDI_PTR_INC | UPDI_DATA_16;
-  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
-    avrdude_message(MSG_INFO, "%s: LD_PTR_INC send operation failed\n", progname);
-    return -1;
-  }
-  return updi_physical_recv(pgm, buffer, words << 2);
-}
-
-int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
-{
-/*
-    def st_ptr_inc(self, data):
-        """
-        Store data to the pointer location with pointer post-increment
-
-        :param data: data to store
-        """
-        self.logger.debug("ST8 to *ptr++")
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8,
-                            data[0]])
-        response = self.updi_phy.receive(1)
-
-        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-            raise PymcuprogError("ACK error with st_ptr_inc")
-
-        num = 1
-        while num < len(data):
-            self.updi_phy.send([data[num]])
-            response = self.updi_phy.receive(1)
-
-            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-                raise PymcuprogError("Error with st_ptr_inc")
-            num += 1
-*/
-  unsigned char send_buffer[3];
-  unsigned char recv_buffer[1];
-  int response;
-  int num = 1;
-  avrdude_message(MSG_DEBUG, "%s: ST8 to *ptr++\n", progname);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_8;
-  send_buffer[2] = buffer[0];
-  if (updi_physical_send(pgm, send_buffer, 3) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR_INC send operation failed\n", progname);
-    return -1;
-  }
-
-  response = updi_physical_recv(pgm, recv_buffer, 1);
-
-  if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
-    avrdude_message(MSG_INFO, "%s: ACK was expected but not received\n", progname);
-    return -1;
-  }
-
-  while (num < size) {
-    send_buffer[0]=buffer[num];
-    if (updi_physical_send(pgm, send_buffer, 1) < 0) {
-      avrdude_message(MSG_INFO, "%s: ST_PTR_INC data send operation failed\n", progname);
-      return -1;
-    }
-    response = updi_physical_recv(pgm, recv_buffer, 1);
-
-    if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
-      avrdude_message(MSG_INFO, "%s: Data ACK was expected but not received\n", progname);
-      return -1;
-    }
-    num++;
-  }
-
-  return 0;
-}
-
-int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words)
-{
-/*
-    def st_ptr_inc16(self, data):
-        """
-        Store a 16-bit word value to the pointer location with pointer post-increment
-
-        :param data: data to store
-        """
-        self.logger.debug("ST16 to *ptr++")
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |
-                            constants.UPDI_DATA_16, data[0], data[1]])
-        response = self.updi_phy.receive(1)
-
-        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-            raise PymcuprogError("ACK error with st_ptr_inc16")
-
-        num = 2
-        while num < len(data):
-            self.updi_phy.send([data[num], data[num + 1]])
-            response = self.updi_phy.receive(1)
-
-            if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-                raise PymcuprogError("Error with st_ptr_inc16")
-            num += 2
-*/
-  unsigned char send_buffer[4];
-  unsigned char recv_buffer[1];
-  int response;
-  int num = 2;
-  avrdude_message(MSG_DEBUG, "%s: ST16 to *ptr++\n", progname);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16;
-  send_buffer[2] = buffer[0];
-  send_buffer[3] = buffer[1];
-  if (updi_physical_send(pgm, send_buffer, 4) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR_INC16 send operation failed\n", progname);
-    return -1;
-  }
-
-  response = updi_physical_recv(pgm, recv_buffer, 1);
-
-  if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
-    avrdude_message(MSG_INFO, "%s: ACK was expected but not received\n", progname);
-    return -1;
-  }
-
-  while (num < words) {
-    send_buffer[0]=buffer[num];
-    send_buffer[1]=buffer[num+1];
-    if (updi_physical_send(pgm, send_buffer, 2) < 0) {
-      avrdude_message(MSG_INFO, "%s: ST_PTR_INC data send operation failed\n", progname);
-      return -1;
-    }
-    response = updi_physical_recv(pgm, recv_buffer, 1);
-
-    if (response != 1 || recv_buffer[0] != UPDI_PHY_ACK) {
-      avrdude_message(MSG_INFO, "%s: Data ACK was expected but not received\n", progname);
-      return -1;
-    }
-    num+=2;
-  }
-
-  return 0;
-}
-
-int updi_link_st_ptr_inc16_RSD(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words, int blocksize) {
-/*
-    def st_ptr_inc16_RSD(self, data, blocksize):
-        """
-        Store a 16-bit word value to the pointer location with pointer post-increment
-        :param data: data to store
-        :blocksize: max number of bytes being sent -1 for all.
-                    Warning: This does not strictly honor blocksize for values < 6
-                    We always glob together the STCS(RSD) and REP commands.
-                    But this should pose no problems for compatibility, because your serial adapter can't deal with 6b chunks,
-                    none of pymcuprog would work!
-        """
-        self.logger.debug("ST16 to *ptr++ with RSD, data length: 0x%03X in blocks of:  %d", len(data), blocksize)
-
-        #for performance we glob everything together into one USB transfer....
-        repnumber= ((len(data) >> 1) -1)
-        data = [*data, *[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA, 0x06]]
-
-        if blocksize == -1 :
-            # Send whole thing at once stcs + repeat + st + (data + stcs)
-            blocksize = 3 + 3 + 2 + len(data)
-        num = 0
-        firstpacket = []
-        if blocksize < 10 :
-            # very small block size - we send pair of 2-byte commands first.
-            firstpacket = [*[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA, 0x0E],
-                            *[constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, (repnumber & 0xFF)]]
-            data = [*[constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC |constants.UPDI_DATA_16], *data]
-            num = 0
-        else:
-            firstpacket = [*[constants.UPDI_PHY_SYNC, constants.UPDI_STCS | constants.UPDI_CS_CTRLA , 0x0E],
-                            *[constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, (repnumber & 0xFF)],
-                            *[constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_16],
-                            *data[:blocksize - 8]]
-            num = blocksize - 8
-        self.updi_phy.send( firstpacket )
-
-        # if finite block size, this is used.
-        while num < len(data):
-            data_slice = data[num:num+blocksize]
-            self.updi_phy.send(data_slice)
-            num += len(data_slice)
-*/
-  avrdude_message(MSG_DEBUG, "%s: ST16 to *ptr++ with RSD, data length: 0x%03X in blocks of: %d\n", progname, words * 2, blocksize);
-
-  unsigned int temp_buffer_size = 3 + 3 + 2 + (words * 2) + 3;
-  unsigned int num=0;
-  unsigned char* temp_buffer = malloc(temp_buffer_size);
-
-  if (temp_buffer == 0) {
-    avrdude_message(MSG_INFO, "%s: Allocating temporary buffer failed\n", progname);
-    return -1;
-  }
-
-  if (blocksize == -1) {
-    blocksize = temp_buffer_size;
-  }
-
-  temp_buffer[0] = UPDI_PHY_SYNC;
-  temp_buffer[1] = UPDI_STCS | UPDI_CS_CTRLA;
-  temp_buffer[2] = 0x0E;
-  temp_buffer[3] = UPDI_PHY_SYNC;
-  temp_buffer[4] = UPDI_REPEAT | UPDI_REPEAT_BYTE;
-  temp_buffer[5] = (words - 1) & 0xFF;
-  temp_buffer[6] = UPDI_PHY_SYNC;
-  temp_buffer[7] = UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16;
-
-  memcpy(temp_buffer + 8, buffer, words * 2);
-
-  temp_buffer[temp_buffer_size-3] = UPDI_PHY_SYNC;
-  temp_buffer[temp_buffer_size-2] = UPDI_STCS | UPDI_CS_CTRLA;
-  temp_buffer[temp_buffer_size-1] = 0x06;
-
-  if (blocksize < 10) {
-    if (updi_physical_send(pgm, temp_buffer, 6) < 0) {
-      avrdude_message(MSG_INFO, "%s: Failed to send first package\n", progname);
-      free(temp_buffer);
-      return -1;
-    }
-    num = 6;
-  } 
-
-  while (num < temp_buffer_size) {
-    int next_package_size;
-
-    if (num + blocksize > temp_buffer_size) {
-      next_package_size = temp_buffer_size - num;
-    } else {
-      next_package_size = blocksize;
-    }
-
-    if (updi_physical_send(pgm, temp_buffer + num, next_package_size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Failed to send package\n", progname);
-      free(temp_buffer);
-      return -1;
-    }
-
-    num+=next_package_size;
-  }
-  free(temp_buffer);
-  return 0;
-}
-
-int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats)
-{
-/*
-    def repeat(self, repeats):
-        """
-        Store a value to the repeat counter
-
-        :param repeats: number of repeats requested
-        """
-        self.logger.debug("Repeat %d", repeats)
-        if (repeats - 1) > constants.UPDI_MAX_REPEAT_SIZE:
-            self.logger.error("Invalid repeat count of %d", repeats)
-            raise Exception("Invalid repeat count!")
-        repeats -= 1
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE,
-                            repeats & 0xFF])
-*/
-  unsigned char buffer[3];
-  avrdude_message(MSG_DEBUG, "%s: Repeat %d\n", progname, repeats);
-  if ((repeats - 1) > UPDI_MAX_REPEAT_SIZE) {
-    avrdude_message(MSG_INFO, "%s: Invalid repeat count of %d\n", progname, repeats);
-    return -1;
-  }
-  repeats-=1;
-  buffer[0] = UPDI_PHY_SYNC;
-  buffer[1] = UPDI_REPEAT | UPDI_REPEAT_BYTE;
-  buffer[2] = repeats & 0xFF;
-  return updi_physical_send(pgm, buffer, 3);
-}
-
-int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
-{
-/*
-    def read_sib(self):
-        """
-        Read the SIB
-        """
-        return self.updi_phy.sib()
-*/
-  return updi_physical_sib(pgm, buffer, size);
-}
-
-int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size)
-{
-/*
-    def key(self, size, key):
-        """
-        Write a key
- 
-        :param size: size of key (0=64B, 1=128B, 2=256B)
-        :param key: key value
-        """
-        self.logger.debug("Writing key")
-        if len(key) != 8 << size:
-            raise PymcuprogError("Invalid KEY length!")
-        self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_KEY | constants.UPDI_KEY_KEY | size])
-        self.updi_phy.send(list(reversed(list(key))))
-*/
-  unsigned char send_buffer[2];
-  unsigned char reversed_key[256];
-  int index;
-  avrdude_message(MSG_DEBUG, "%s: UPDI writing key\n", progname);
-  if (size != (8 << size_type)) {
-    avrdude_message(MSG_INFO, "%s: Invalid key length\n", progname);
-    return -1;
-  }
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_KEY | UPDI_KEY_KEY | size_type;
-  if (updi_physical_send(pgm, send_buffer, 2) < 0) {
-    avrdude_message(MSG_INFO, "%s: UPDI key send message failed\n", progname);
-    return -1;
-  }
-  /* reverse key contents */
-  for (index=0; index<size; index++) {
-    reversed_key[index] = buffer[size-index-1];
-  }
-  return updi_physical_send(pgm, reversed_key, size);
-}
-
-int updi_link_ld(PROGRAMMER * pgm, uint32_t address, uint8_t * value)
-{
-/*
-    def ld(self, address):
-        """
-        Load a single byte direct from a 24-bit address
-
-        :param address: address to load from
-        :return: value read
-        """
-        self.logger.info("LD from 0x{0:06X}".format(address))
-        self.updi_phy.send(
-            [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8,
-             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
-        return self.updi_phy.receive(1)[0]
-*/
-  unsigned char send_buffer[5];
-  unsigned char recv_buffer[1];
-  avrdude_message(MSG_DEBUG, "%s: LD from 0x%06X\n", progname, address);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_LDS | UPDI_DATA_8 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
-  send_buffer[2] = address & 0xFF;
-  send_buffer[3] = (address >> 8) & 0xFF;
-  send_buffer[4] = (address >> 16) & 0xFF;
-  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
-    avrdude_message(MSG_INFO, "%s: LD operation send failed\n", progname);
-    return -1;
-  }
-  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: LD operation recv failed\n", progname);
-    return -1;
-  }
-  * value = recv_buffer[0];
-  return 0;
-}
-
-int updi_link_ld16(PROGRAMMER * pgm, uint32_t address, uint16_t * value)
-{
-/*
-    def ld16(self, address):
-        """
-        Load a 16-bit word directly from a 24-bit address
-
-        :param address: address to load from
-        :return: values read
-        """
-        self.logger.info("LD from 0x{0:06X}".format(address))
-        self.updi_phy.send(
-            [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16,
-             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
-        return self.updi_phy.receive(2)
-*/
-  unsigned char send_buffer[5];
-  unsigned char recv_buffer[2];
-  avrdude_message(MSG_DEBUG, "%s: LD16 from 0x%06X\n", progname, address);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_LDS | UPDI_DATA_16 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
-  send_buffer[2] = address & 0xFF;
-  send_buffer[3] = (address >> 8) & 0xFF;
-  send_buffer[4] = (address >> 16) & 0xFF;
-  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
-    avrdude_message(MSG_INFO, "%s: LD16 operation send failed\n", progname);
-    return -1;
-  }
-  if (updi_physical_recv(pgm, recv_buffer, 2) < 0) {
-    avrdude_message(MSG_INFO, "%s: LD16 operation recv failed\n", progname);
-    return -1;
-  }
-  * value = (recv_buffer[0] << 8 | recv_buffer[1]);
-  return 0;
-}
-
-static int updi_link_st_data_phase(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size)
-{
-/*
-    def _st_data_phase(self, values):
-        """
-        Performs data phase of transaction:
-        * receive ACK
-        * send data
-
-        :param values: bytearray of value(s) to send
-        """
-        response = self.updi_phy.receive(1)
-        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-            raise PymcuprogError("Error with st")
-
-        self.updi_phy.send(values)
-        response = self.updi_phy.receive(1)
-        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-            raise PymcuprogError("Error with st")
-*/
-  unsigned char recv_buffer[1];
-  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: UPDI data phase recv failed on first ACK\n", progname);
-    return -1;
-  }
-  if (recv_buffer[0] != UPDI_PHY_ACK) {
-    avrdude_message(MSG_INFO, "%s: UPDI data phase expected first ACK\n", progname);
-    return -1;
-  }
-  if (updi_physical_send(pgm, buffer, size) < 0) {
-    avrdude_message(MSG_INFO, "%s: UPDI data phase send failed\n", progname);
-    return -1;
-  }
-  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: UPDI data phase recv failed on second ACK\n", progname);
-    return -1;
-  }
-  if (recv_buffer[0] != UPDI_PHY_ACK) {
-    avrdude_message(MSG_INFO, "%s: UPDI data phase expected second ACK\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-int updi_link_st(PROGRAMMER * pgm, uint32_t address, uint8_t value)
-{
-/*
-    def st(self, address, value):
-        """
-        Store a single byte value directly to a 24-bit address
-
-        :param address: address to write to
-        :param value: value to write
-        """
-        self.logger.info("ST to 0x{0:06X}".format(address))
-        self.updi_phy.send(
-            [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8,
-             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
-        return self._st_data_phase([value & 0xFF])
-*/
-  unsigned char send_buffer[5];
-  avrdude_message(MSG_DEBUG, "%s: ST to 0x%06X\n", progname, address);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_STS | UPDI_DATA_8 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
-  send_buffer[2] = address & 0xFF;
-  send_buffer[3] = (address >> 8) & 0xFF;
-  send_buffer[4] = (address >> 16) & 0xFF;
-  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST operation send failed\n", progname);
-    return -1;
-  }
-  send_buffer[0] = value;
-  return updi_link_st_data_phase(pgm, send_buffer, 1);
-}
-
-int updi_link_st16(PROGRAMMER * pgm, uint32_t address, uint16_t value)
-{
-/*
-    def st16(self, address, value):
-        """
-        Store a 16-bit word value directly to a 24-bit address
-
-        :param address: address to write to
-        :param value: value to write
-        """
-        self.logger.info("ST to 0x{0:06X}".format(address))
-        self.updi_phy.send(
-            [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16,
-             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
-        return self._st_data_phase([value & 0xFF, (value >> 8) & 0xFF])
-*/
-  unsigned char send_buffer[5];
-  avrdude_message(MSG_DEBUG, "%s: ST16 to 0x%06X\n", progname, address);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_STS | UPDI_DATA_16 | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_ADDRESS_24 : UPDI_ADDRESS_16);
-  send_buffer[2] = address & 0xFF;
-  send_buffer[3] = (address >> 8) & 0xFF;
-  send_buffer[4] = (address >> 16) & 0xFF;
-  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST16 operation send failed\n", progname);
-    return -1;
-  }
-  send_buffer[0] = value & 0xFF;
-  send_buffer[1] = (value >> 8) & 0xFF;
-  return updi_link_st_data_phase(pgm, send_buffer, 2);
-}
-
-int updi_link_st_ptr(PROGRAMMER * pgm, uint32_t address)
-{
-/*
-    def st_ptr(self, address):
-        """
-        Set the pointer location
-
-        :param address: address to write
-        """
-        self.logger.info("ST to ptr")
-        self.updi_phy.send(
-            [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_24,
-             address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF])
-        response = self.updi_phy.receive(1)
-        if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK:
-            raise PymcuprogError("Error with st_ptr")
-*/
-  unsigned char send_buffer[5];
-  unsigned char recv_buffer[1];
-  avrdude_message(MSG_DEBUG, "%s: ST_PTR to 0x%06X\n", progname, address);
-  send_buffer[0] = UPDI_PHY_SYNC;
-  send_buffer[1] = UPDI_STS | UPDI_ST | UPDI_PTR_ADDRESS | (updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? UPDI_DATA_24 : UPDI_DATA_16);
-  send_buffer[2] = address & 0xFF;
-  send_buffer[3] = (address >> 8) & 0xFF;
-  send_buffer[4] = (address >> 16) & 0xFF;
-  if (updi_physical_send(pgm, send_buffer, updi_get_datalink_mode(pgm) == UPDI_LINK_MODE_24BIT ? 5 : 4) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR operation send failed\n", progname);
-    return -1;
-  }
-  if (updi_physical_recv(pgm, recv_buffer, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: UPDI ST_PTR recv failed on ACK\n", progname);
-    return -1;
-  }
-  if (recv_buffer[0] != UPDI_PHY_ACK) {
-    avrdude_message(MSG_INFO, "%s: UPDI ST_PTR expected ACK\n", progname);
-    return -1;
-  }
-  return 0;
-}
diff --git a/avrdude/updi_link.h b/avrdude/updi_link.h
deleted file mode 100644
index 5b5e5bda..00000000
--- a/avrdude/updi_link.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#ifndef updi_link_h
-#define updi_link_h
-
-#include "libavrdude.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int updi_link_open(PROGRAMMER * pgm);
-void updi_link_close(PROGRAMMER * pgm);
-int updi_link_init(PROGRAMMER * pgm);
-int updi_link_ldcs(PROGRAMMER * pgm, uint8_t address, uint8_t * value);
-int updi_link_stcs(PROGRAMMER * pgm, uint8_t address, uint8_t value);
-int updi_link_ld_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
-int updi_link_ld_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
-int updi_link_st_ptr_inc(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
-int updi_link_st_ptr_inc16(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words);
-int updi_link_st_ptr_inc16_RSD(PROGRAMMER * pgm, unsigned char * buffer, uint16_t words, int blocksize);
-int updi_link_repeat(PROGRAMMER * pgm, uint16_t repeats);
-int updi_link_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
-int updi_link_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
-int updi_link_ld(PROGRAMMER * pgm, uint32_t address, uint8_t * value);
-int updi_link_ld16(PROGRAMMER * pgm, uint32_t address, uint16_t * value);
-int updi_link_st(PROGRAMMER * pgm, uint32_t address, uint8_t value);
-int updi_link_st16(PROGRAMMER * pgm, uint32_t address, uint16_t value);
-int updi_link_st_ptr(PROGRAMMER * pgm, uint32_t address);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* updi_link_h */
diff --git a/avrdude/updi_nvm.c b/avrdude/updi_nvm.c
deleted file mode 100644
index f3d899a9..00000000
--- a/avrdude/updi_nvm.c
+++ /dev/null
@@ -1,1275 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#include "ac_cfg.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "avrdude.h"
-#include "libavrdude.h"
-#include "updi_nvm.h"
-#include "updi_state.h"
-#include "updi_constants.h"
-#include "updi_readwrite.h"
-
-typedef enum 
-{
-  DONT_USE_WORD_ACCESS,
-  USE_WORD_ACCESS
-} access_mode;
-
-#define USE_DEFAULT_COMMAND 0xFF
-
-static int nvm_chip_erase_V0(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def chip_erase(self):
-        """
-        Does a chip erase using the NVM controller
-
-        Note that on locked devices this is not possible
-        and the ERASE KEY has to be used instead, see the unlock method
-        """
-        self.logger.info("Chip erase using NVM CTRL")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before chip erase")
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE)
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready after chip erase")
-
-        return True
-*/
-  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_flash_page_V0(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
-{
-/*
-    def erase_flash_page(self, address):
-        """
-        Erasing single flash page using the NVM controller (v0)
-
-        :param address: Start address of page to erase
-        :type address: int
-        """
-        self.logger.info("Erase flash page at address 0x%08X", address)
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
-
-        # Dummy write
-        self.readwrite.write_data(address, [0xFF])
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE)
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
-*/
-  unsigned char data[1];
-  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  data[0] = 0xFF;
-  if (updi_write_data(pgm, address, data, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_eeprom_V0(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def erase_eeprom(self):
-        """
-        Erase EEPROM memory only (v0)
-        """
-        self.logger.info("Erase EEPROM")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM)
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
-*/  
-  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM) < 0) {
-    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_user_row_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
-{
-/*
-    def erase_user_row(self, address, size):
-        """
-        Erase User Row memory only (v0)
-
-        :param address: Start address of user row
-        :type address: int
-        """
-        self.logger.info("Erase user row")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before user row erase")
-
-        # On this NVM version user row is implemented as EEPROM
-        # When erasing single EEPROM pages a dummy write is needed for each location to be erased
-        for offset in range(size):
-            self.readwrite.write_data(address+offset, [0xFF])
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE)
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready after user row erase")
-*/
-  uint16_t offset;
-  unsigned char data[1];
-  avrdude_message(MSG_DEBUG, "%s: Erase user row\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  data[0]=0xFF;
-  for (offset = 0; offset<size; offset++)
-  {
-    if (updi_write_data(pgm, address+offset, data, 1) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data operation failed at offset 0x%04x\n", progname, offset);
-      return -1;
-    }
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Erase page operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_write_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
-                        uint16_t size, access_mode mode, uint8_t nvm_command);
-
-static int nvm_write_eeprom_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
-
-
-static int nvm_write_flash_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_flash(self, address, data):
-        """
-        Writes data to flash (v0)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_nvm(address, data, use_word_access=True)
-*/
-  return nvm_write_V0(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
-}
-
-static int nvm_write_user_row_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_user_row(self, address, data):
-        """
-        Writes data to user row (v0)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        # On this NVM variant user row is implemented as EEPROM
-        return self.write_eeprom(address, data)
-*/
-  return nvm_write_eeprom_V0(pgm, p, address, buffer, size);
-}
-
-static int nvm_write_eeprom_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_eeprom(self, address, data):
-        """
-        Write data to EEPROM (v0)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_nvm(address, data, use_word_access=False,
-                              nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE)
-*/
-  return nvm_write_V0(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS, UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE);
-}
-
-static int nvm_write_fuse_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
-{
-/*
-    def write_fuse(self, address, data):
-        """
-        Writes one fuse value (v0)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-
-        # Check that NVM controller is ready
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready before fuse write")
-
-        # Write address to NVMCTRL ADDR
-        self.logger.debug("Load NVM address")
-        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL, address & 0xFF)
-        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF)
-
-        # Write data
-        self.logger.debug("Load fuse data")
-        self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL, data[0] & 0xFF)
-
-        # Execute
-        self.logger.debug("Execute fuse write")
-        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE)
-
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready after fuse write")
-*/
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Load NVM address\n", progname);
-  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_ADDRL, address & 0xFF) < 0) {
-    avrdude_message(MSG_INFO, "%s: Write ADDRL operation failed\n", progname);
-    return -1;
-  }
-  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF) < 0) {
-    avrdude_message(MSG_INFO, "%s: Write ADDRH operation failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Load fuse data\n", progname);
-  if (updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_DATAL, value & 0xFF) < 0) {
-    avrdude_message(MSG_INFO, "%s: Write DATAL operation failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Execute fuse write\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Write fuse operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_write_V0(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
-                        uint16_t size, access_mode mode, uint8_t nvm_command)
-{
-/*
-    def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE):
-        """
-        Writes a page of data to NVM (v0)
-
-        By default the PAGE_WRITE command is used, which
-        requires that the page is already erased.
-        By default word access is used (flash)
-
-        :param address: address to write to
-        :param data: data to write
-        :param use_word_access: write whole words?
-        :param nvmcommand: command to use for commit
-        """
-
-        # Check that NVM controller is ready
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear")
-
-        # Clear the page buffer
-        self.logger.debug("Clear page buffer")
-        self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR)
-
-        # Wait for NVM controller to be ready
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear")
-
-        # Load the page buffer by writing directly to location
-        if use_word_access:
-            self.readwrite.write_data_words(address, data)
-        else:
-            self.readwrite.write_data(address, data)
-
-        # Write the page to NVM, maybe erase first
-        self.logger.debug("Committing data")
-        self.execute_nvm_command(nvmcommand)
-
-        # Wait for NVM controller to be ready again
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write")
-*/
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Clear page buffer\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) < 0) {
-    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (mode == USE_WORD_ACCESS) {
-    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
-      return -1;
-    }
-  } else {
-    if (updi_write_data(pgm, address, buffer, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
-      return -1;
-    }
-  }
-  avrdude_message(MSG_DEBUG, "%s: Committing data\n", progname);
-  if (nvm_command == USE_DEFAULT_COMMAND) {
-    nvm_command = UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE;
-  }
-  if (updi_nvm_command(pgm, p, nvm_command) < 0) {
-      avrdude_message(MSG_INFO, "%s: Commit data command failed\n", progname);
-      return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_chip_erase_V2(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def chip_erase(self):
-        """
-        Does a chip erase using the NVM controller
-        Note that on locked devices this it not possible
-        and the ERASE KEY has to be used instead
-        """
-        self.logger.info("Chip erase using NVM CTRL")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise Exception("Timeout waiting for NVM controller to be ready before chip erase")
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE)
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise Exception("Timeout waiting for NVM controller to be ready after chip erase")
-
-        return True
-*/
-  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_flash_page_V2(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
-{
-/*
-    def erase_flash_page(self, address):
-        """
-        Erasing single flash page using the NVM controller (v1)
-
-        :param address: Start address of page to erase
-        :type address: int
-        """
-        self.logger.info("Erase flash page at address 0x%08X", address)
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
-
-        # Erase command
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE)
-
-        # Dummy write
-        self.readwrite.write_data(address, [0xFF])
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
-
-        # Remove command from NVM controller
-        self.logger.debug("Clear NVM command")
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
-*/
-  unsigned char data[1];
-  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  data[0] = 0xFF;
-  if (updi_write_data(pgm, address, data, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_eeprom_V2(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def erase_eeprom(self):
-        """
-        Erase EEPROM memory only (v1)
-        """
-        self.logger.info("Erase EEPROM")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE)
-
-        # And wait for it
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
-
-        # Remove command from NVM controller
-        self.logger.debug("Clear NVM command")
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
-*/
-  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
-    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_user_row_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
-{
-/*
-    def erase_user_row(self, address, size):
-        """
-        Erase User Row memory only (v1)
-
-        :param address: Start address of user row
-        :type address: int
-        """
-        # size is not used for this NVM version
-        _dummy = size
-        # On this NVM version user row is implemented as flash
-        return self.erase_flash_page(address)
-*/
-  return nvm_erase_flash_page_V2(pgm, p, address);
-}
-
-static int nvm_write_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
-                        uint16_t size, access_mode mode);
-
-static int nvm_write_flash_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_flash(self, address, data):
-        """
-        Writes data to flash (v1)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_nvm(address, data, use_word_access=True)
-*/
-  return nvm_write_V2(pgm, p, address, buffer, size, USE_WORD_ACCESS);
-}
-
-static int nvm_write_user_row_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_user_row(self, address, data):
-        """
-        Writes data to user row (v1)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        # On this NVM variant user row is implemented as Flash
-        return self.write_nvm(address, data, use_word_access=False)
-*/
-  return nvm_write_V2(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS);
-}
-
-static int nvm_write_eeprom_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_eeprom(self, address, data):
-        """
-        Writes data to NVM (EEPROM)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE
-
-        # Check that NVM controller is ready
-        if not self.wait_nvm_ready():
-            raise Exception("Timeout waiting for NVM ready before command write")
-
-        # Write the command to the NVM controller
-        self.logger.info("NVM EEPROM erase/write command")
-        self.execute_nvm_command(nvm_command)
-
-        # Write the data
-        self.readwrite.write_data(address, data)
-
-        # Wait for NVM controller to be ready again
-        if not self.wait_nvm_ready():
-            raise Exception("Timeout waiting for NVM ready after data write")
-
-        # Remove command from NVM controller
-        self.logger.info("Clear NVM command")
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
-*/
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: NVM EEPROM erase/write command\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE) < 0) {
-    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_write_data(pgm, address, buffer, size) < 0) {
-    avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
-    avrdude_message(MSG_INFO, "%s: Clear NVM command failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_write_fuse_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
-{
-/*
-    def write_fuse(self, address, data):
-        """
-        Writes one fuse value
-        V1 fuses are EEPROM-based
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_eeprom(address, data)
-*/
-  unsigned char buffer[1];
-  buffer[0]=value;
-  return nvm_write_eeprom_V2(pgm, p, address, buffer, 1);
-}
-
-static int nvm_write_V2(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
-                        uint16_t size, access_mode mode)
-{
-/*
-    def write_nvm(self, address, data, use_word_access):
-        """
-        Writes data to NVM (version 1)
-        This version of the NVM block has no page buffer, so words are written directly.
-
-        :param address: address to write to
-        :param data: data to write
-        :param use_word_access: write in whole words?
-        """
-        nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE
-
-        # Check that NVM controller is ready
-        if not self.wait_nvm_ready():
-            raise Exception("Timeout waiting for NVM controller to be ready before page buffer clear")
-
-        # Write the command to the NVM controller
-        self.logger.info("NVM write command")
-        self.execute_nvm_command(nvm_command)
-
-        # Write the data
-        if use_word_access:
-            self.readwrite.write_data_words(address, data)
-        else:
-            self.readwrite.write_data(address, data)
-
-        # Wait for NVM controller to be ready again
-        if not self.wait_nvm_ready():
-            raise Exception("Timeout waiting for NVM controller to be ready after data write")
-
-        # Remove command from NVM controller
-        self.logger.info("Clear NVM command")
-        self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD)
-*/
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: NVM write command\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
-    return -1;
-  }
-  if (mode == USE_WORD_ACCESS) {
-    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
-      return -1;
-    }
-  } else {
-    if (updi_write_data(pgm, address, buffer, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
-      return -1;
-    }
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Clear NVM command\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V2_NVMCTRL_CTRLA_NOCMD) < 0) {
-    avrdude_message(MSG_INFO, "%s: Clear NVM command failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_chip_erase_V3(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def chip_erase(self):
-        """
-        Does a chip erase using the NVM controller
-
-        Note that on locked devices this is not possible
-        and the ERASE KEY has to be used instead, see the unlock method
-        """
-        self.logger.info("Chip erase using NVM CTRL")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before chip erase")
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE)
-
-        # And wait for it
-        status = self.wait_nvm_ready()
-
-        # Remove command
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
-
-        if not status:
-            raise IOError("Timeout waiting for NVM controller to be ready after chip erase")
-
-        return True
-*/
-  avrdude_message(MSG_DEBUG, "%s: Chip erase using NVM CTRL\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Chip erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
-    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_flash_page_V3(PROGRAMMER * pgm, AVRPART * p, uint32_t address)
-{
-/*
-    def erase_flash_page(self, address):
-        """
-        Erasing single flash page using the NVM controller (v3)
-
-        :param address: Start address of page to erase
-        :type address: int
-        """
-        self.logger.info("Erase flash page at address 0x%08X", address)
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before flash page erase")
-
-        # Dummy write
-        self.readwrite.write_data(address, [0xFF])
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE)
-
-        # And wait for it
-        status = self.wait_nvm_ready()
-
-        # Remove command
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
-
-        if not status:
-            raise IOError("Timeout waiting for NVM controller to be ready after flash page erase")
-*/  
-  unsigned char data[1];
-  avrdude_message(MSG_DEBUG, "%s: Erase flash page at address 0x%06X\n", progname, address);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  data[0] = 0xFF;
-  if (updi_write_data(pgm, address, data, 1) < 0) {
-    avrdude_message(MSG_INFO, "%s: Dummy write operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: Flash page erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_eeprom_V3(PROGRAMMER * pgm, AVRPART * p)
-{
-/*
-    def erase_eeprom(self):
-        """
-        Erase EEPROM memory only
-        """
-        self.logger.info("Erase EEPROM")
-
-        # Wait until NVM CTRL is ready to erase
-        if not self.wait_nvm_ready():
-            raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase")
-
-        # Erase
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE)
-
-        # And wait for it
-        status = self.wait_nvm_ready()
-
-        # Remove command
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
-
-        if not status:
-            raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase")
-*/
-  avrdude_message(MSG_DEBUG, "%s: Erase EEPROM\n", progname);
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE) < 0) {
-    avrdude_message(MSG_INFO, "%s: EEPROM erase command failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
-    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-static int nvm_erase_user_row_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
-{
-/*
-    def erase_user_row(self, address, size):
-        """
-        Erase User Row memory only
-
-        :param address: Start address of user row
-        :type address: int
-        """
-        self.logger.info("Erase user row")
-
-        # On this NVM version user row is implemented as FLASH
-        return self.erase_flash_page(self, address)
-*/
-  avrdude_message(MSG_DEBUG, "%s: Erase user row at address 0x%06X\n", progname, address);
-  
-  return nvm_erase_flash_page_V3(pgm, p, address);
-}
-
-static int nvm_write_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
-                        uint16_t size, access_mode mode, uint8_t nvm_command);
-
-static int nvm_write_flash_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_flash(self, address, data):
-        """
-        Writes data to flash (v3)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_nvm(address, data, use_word_access=True)
-*/
-  return nvm_write_V3(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
-}
-
-static int nvm_write_user_row_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_user_row(self, address, data):
-        """
-        Writes data to user row (v3)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        # On this NVM variant user row is implemented as FLASH
-        return self.write_nvm(address, data, use_word_access=True)
-*/
-  return nvm_write_V3(pgm, p, address, buffer, size, USE_WORD_ACCESS, USE_DEFAULT_COMMAND);
-}
-
-static int nvm_write_eeprom_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-/*
-    def write_eeprom(self, address, data):
-        """
-        Write data to EEPROM (v3)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_nvm(address, data, use_word_access=False,
-                              nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE)
-*/
-  return nvm_write_V3(pgm, p, address, buffer, size, DONT_USE_WORD_ACCESS, UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE);
-}
-
-static int nvm_write_fuse_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
-{
-/*
-    def write_fuse(self, address, data):
-        """
-        Writes one fuse value (v3)
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        return self.write_eeprom(address, data)
-*/
-  unsigned char buffer[1];
-  buffer[0] = value;
-  return nvm_write_eeprom_V3(pgm, p, address, buffer, 1);
-}
-
-static int nvm_write_V3(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, 
-                        uint16_t size, access_mode mode, uint8_t nvm_command)
-{
-/*
-    def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE):
-        """
-        Writes a page of data to NVM (v3)
-
-        By default the PAGE_WRITE command is used, which
-        requires that the page is already erased.
-        By default word access is used (flash)
-
-        :param address: address to write to
-        :param data: data to write
-        :param use_word_access: write whole words?
-        :param nvmcommand: command to use for commit
-        """
-
-        # Check that NVM controller is ready
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear")
-
-        # Clear the page buffer
-        self.logger.debug("Clear page buffer")
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR)
-
-        # Wait for NVM controller to be ready
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear")
-
-        # Load the page buffer by writing directly to location
-        if use_word_access:
-            self.readwrite.write_data_words(address, data)
-        else:
-            self.readwrite.write_data(address, data)
-
-        # Write the page to NVM, maybe erase first
-        self.logger.debug("Committing data")
-        self.execute_nvm_command(nvmcommand)
-
-        # Wait for NVM controller to be ready again
-        if not self.wait_nvm_ready():
-            raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write")
-
-        # Remove command
-        self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD)
-*/
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  avrdude_message(MSG_DEBUG, "%s: Clear page buffer\n", progname);
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR) < 0) {
-    avrdude_message(MSG_INFO, "%s: Clear page operation failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (mode == USE_WORD_ACCESS) {
-    if (updi_write_data_words(pgm, address, buffer, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data words operation failed\n", progname);
-      return -1;
-    }
-  } else {
-    if (updi_write_data(pgm, address, buffer, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Write data operation failed\n", progname);
-      return -1;
-    }
-  }
-  avrdude_message(MSG_DEBUG, "%s: Committing data\n", progname);
-  if (nvm_command == USE_DEFAULT_COMMAND) {
-    nvm_command = UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE;
-  }
-  if (updi_nvm_command(pgm, p, nvm_command) < 0) {
-      avrdude_message(MSG_INFO, "%s: Commit data command failed\n", progname);
-      return -1;
-  }
-  if (updi_nvm_wait_ready(pgm, p) < 0) {
-    avrdude_message(MSG_INFO, "%s: Wait for ready chip failed\n", progname);
-    return -1;
-  }
-  if (updi_nvm_command(pgm, p, UPDI_V3_NVMCTRL_CTRLA_NOCMD) < 0) {
-    avrdude_message(MSG_INFO, "%s: Sending empty command failed\n", progname);
-    return -1;
-  }
-  return 0;
-}
-
-
-int updi_nvm_chip_erase(PROGRAMMER * pgm, AVRPART * p)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_chip_erase_V0(pgm, p);
-    case UPDI_NVM_MODE_V2:
-      return nvm_chip_erase_V2(pgm, p);
-    case UPDI_NVM_MODE_V3:
-      return nvm_chip_erase_V3(pgm, p);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_erase_flash_page(PROGRAMMER * pgm, AVRPART *p, uint32_t address)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_erase_flash_page_V0(pgm, p, address);
-    case UPDI_NVM_MODE_V2:
-      return nvm_erase_flash_page_V2(pgm, p, address);
-    case UPDI_NVM_MODE_V3:
-      return nvm_erase_flash_page_V3(pgm, p, address);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_erase_eeprom(PROGRAMMER * pgm, AVRPART *p)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_erase_eeprom_V0(pgm, p);
-    case UPDI_NVM_MODE_V2:
-      return nvm_erase_eeprom_V2(pgm, p);
-    case UPDI_NVM_MODE_V3:
-      return nvm_erase_eeprom_V3(pgm, p);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_erase_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_erase_user_row_V0(pgm, p, address, size);
-    case UPDI_NVM_MODE_V2:
-      return nvm_erase_user_row_V2(pgm, p, address, size);
-    case UPDI_NVM_MODE_V3:
-      return nvm_erase_user_row_V3(pgm, p, address, size);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_write_flash(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_write_flash_V0(pgm, p, address, buffer, size);
-    case UPDI_NVM_MODE_V2:
-      return nvm_write_flash_V2(pgm, p, address, buffer, size);
-    case UPDI_NVM_MODE_V3:
-      return nvm_write_flash_V3(pgm, p, address, buffer, size);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_write_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_write_user_row_V0(pgm, p, address, buffer, size);
-    case UPDI_NVM_MODE_V2:
-      return nvm_write_user_row_V2(pgm, p, address, buffer, size);
-    case UPDI_NVM_MODE_V3:
-      return nvm_write_user_row_V3(pgm, p, address, buffer, size);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_write_eeprom(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_write_eeprom_V0(pgm, p, address, buffer, size);
-    case UPDI_NVM_MODE_V2:
-      return nvm_write_eeprom_V2(pgm, p, address, buffer, size);
-    case UPDI_NVM_MODE_V3:
-      return nvm_write_eeprom_V3(pgm, p, address, buffer, size);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_write_fuse(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value)
-{
-  switch(updi_get_nvm_mode(pgm))
-  {
-    case UPDI_NVM_MODE_V0:
-      return nvm_write_fuse_V0(pgm, p, address, value);
-    case UPDI_NVM_MODE_V2:
-      return nvm_write_fuse_V2(pgm, p, address, value);
-    case UPDI_NVM_MODE_V3:
-      return nvm_write_fuse_V3(pgm, p, address, value);
-    default:
-      avrdude_message(MSG_INFO, "%s: Invalid NVM Mode %d\n", progname, updi_get_nvm_mode(pgm));
-      return -1;
-  }
-}
-
-int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p)
-{
-/*
-    def wait_nvm_ready(self):
-        """
-        Waits for the NVM controller to be ready
-        """
-        timeout = Timeout(10000)  # 10 sec timeout, just to be sure
-
-        self.logger.debug("Wait NVM ready")
-        while not timeout.expired():
-            status = self.readwrite.read_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_STATUS)
-            if status & (1 << constants.UPDI_NVM_STATUS_WRITE_ERROR):
-                self.logger.error("NVM error")
-                return False
-
-            if not status & ((1 << constants.UPDI_NVM_STATUS_EEPROM_BUSY) |
-                             (1 << constants.UPDI_NVM_STATUS_FLASH_BUSY)):
-                return True
-
-        self.logger.error("Wait NVM ready timed out")
-        return False
-*/
-  unsigned long start_time;
-  unsigned long current_time;
-  struct timeval tv;
-  uint8_t status;
-  gettimeofday (&tv, NULL);
-  start_time = (tv.tv_sec * 1000000) + tv.tv_usec;
-  do {
-    if (updi_read_byte(pgm, p->nvm_base + UPDI_NVMCTRL_STATUS, &status) >= 0) {
-      if (status & (1 << UPDI_NVM_STATUS_WRITE_ERROR)) {
-        avrdude_message(MSG_INFO, "%s: NVM error\n", progname);
-        return -1;
-      }
-      if (!(status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | 
-                      (1 << UPDI_NVM_STATUS_FLASH_BUSY)))) {
-        return 0;
-      }
-    }
-    gettimeofday (&tv, NULL);
-    current_time = (tv.tv_sec * 1000000) + tv.tv_usec;
-  } while ((current_time - start_time) < 10000000);
-
-  avrdude_message(MSG_INFO, "%s: Wait NVM ready timed out\n", progname);
-  return -1;
-}
-
-int updi_nvm_command(PROGRAMMER * pgm, AVRPART *p, uint8_t command)
-{
-/*
-    def execute_nvm_command(self, command):
-        """
-        Executes an NVM COMMAND on the NVM CTRL
-
-        :param command: command to execute
-        """
-        self.logger.debug("NVMCMD %d executing", command)
-        return self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_CTRLA, command)
-*/
-  avrdude_message(MSG_DEBUG, "%s: NVMCMD %d executing\n", progname, command);
-
-  return updi_write_byte(pgm, p->nvm_base + UPDI_NVMCTRL_CTRLA, command);
-}
diff --git a/avrdude/updi_nvm.h b/avrdude/updi_nvm.h
deleted file mode 100644
index c0c1544e..00000000
--- a/avrdude/updi_nvm.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#ifndef updi_nvm_h
-#define updi_nvm_h
-
-#include "libavrdude.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int updi_nvm_chip_erase(PROGRAMMER * pgm, AVRPART * p);
-int updi_nvm_erase_flash_page(PROGRAMMER * pgm, AVRPART *p, uint32_t address);
-int updi_nvm_erase_eeprom(PROGRAMMER * pgm, AVRPART *p);
-int updi_nvm_erase_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint16_t size);
-int updi_nvm_write_flash(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
-int updi_nvm_write_user_row(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
-int updi_nvm_write_eeprom(PROGRAMMER * pgm, AVRPART *p, uint32_t address, unsigned char * buffer, uint16_t size);
-int updi_nvm_write_fuse(PROGRAMMER * pgm, AVRPART *p, uint32_t address, uint8_t value);
-int updi_nvm_wait_ready(PROGRAMMER * pgm, AVRPART *p);
-int updi_nvm_command(PROGRAMMER * pgm, AVRPART *p, uint8_t command);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* updi_nvm_h */
diff --git a/avrdude/updi_readwrite.c b/avrdude/updi_readwrite.c
deleted file mode 100644
index e304a510..00000000
--- a/avrdude/updi_readwrite.c
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#include "ac_cfg.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include "avrdude.h"
-#include "libavrdude.h"
-#include "updi_constants.h"
-#include "updi_link.h"
-#include "updi_readwrite.h"
-
-int updi_read_cs(PROGRAMMER * pgm, uint8_t address, uint8_t * value)
-{
-/*
-    def read_cs(self, address):
-        """
-        Read from Control/Status space
-
-        :param address: address (index) to read
-        :return: value read
-        """
-        return self.datalink.ldcs(address)
-*/
-  return updi_link_ldcs(pgm, address, value);
-}
-
-int updi_write_cs(PROGRAMMER * pgm, uint8_t address, uint8_t value)
-{
-/*
-    def write_cs(self, address, value):
-        """
-        Write to Control/Status space
-
-        :param address: address (index) to write
-        :param value: 8-bit value to write
-        """
-        return self.datalink.stcs(address, value)
-*/
-  return updi_link_stcs(pgm, address, value);
-}
-
-int updi_write_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size)
-{
-/*
-    def write_key(self, size, key):
-        """
-        Write a KEY into UPDI
-
-        :param size: size of key to send
-        :param key: key value
-        """
-        return self.datalink.key(size, key)
-*/
-  return updi_link_key(pgm, buffer, size_type, size);
-}
-
-int updi_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size)
-{
-/*
-    def read_sib(self):
-        """
-        Read the SIB from UPDI
-
-        :return: SIB string (bytearray) read
-        """
-        return self.datalink.read_sib()
-*/
-  return updi_link_read_sib(pgm, buffer, size);
-}
-
-int updi_read_byte(PROGRAMMER * pgm, uint32_t address, uint8_t * value)
-{
-/*
-    def read_byte(self, address):
-        """
-        Read a single byte from UPDI
-
-        :param address: address to read from
-        :return: value read
-        """
-        return self.datalink.ld(address)
-*/
-  return updi_link_ld(pgm, address, value);
-}
-
-int updi_write_byte(PROGRAMMER * pgm, uint32_t address, uint8_t value)
-{
-/*
-    def write_byte(self, address, value):
-        """
-        Writes a single byte to UPDI
-
-        :param address: address to write to
-        :param value: value to write
-        """
-        return self.datalink.st(address, value)
-*/
-  return updi_link_st(pgm, address, value);
-}
-
-int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
-{
-/*
-    def read_data(self, address, size):
-        """
-        Reads a number of bytes of data from UPDI
-
-        :param address: address to write to
-        :param size: number of bytes to read
-        """
-        self.logger.debug("Reading %d bytes from 0x%04X", size, address)
-        # Range check
-        if size > constants.UPDI_MAX_REPEAT_SIZE:
-            raise PymcuprogError("Cant read that many bytes in one go")
-
-        # Store the address
-        self.datalink.st_ptr(address)
-
-        # Fire up the repeat
-        if size > 1:
-            self.datalink.repeat(size)
-
-        # Do the read(s)
-        return self.datalink.ld_ptr_inc(size)
-*/
-  avrdude_message(MSG_DEBUG, "%s: Reading %d bytes from 0x%06X\n", progname, size, address);
-
-  if (size > UPDI_MAX_REPEAT_SIZE) {
-    avrdude_message(MSG_INFO, "%s: Can't read that many bytes in one go\n", progname);
-    return -1;
-  }
-
-  if (updi_link_st_ptr(pgm, address) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
-    return -1;
-  }
-
-  if (size > 1) {
-    if (updi_link_repeat(pgm, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
-      return -1;
-    }
-  }
-  return updi_link_ld_ptr_inc(pgm, buffer, size);
-}
-
-int updi_write_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
-{
-/*
-    def write_data(self, address, data):
-        """
-        Writes a number of bytes to memory
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        # Special case of 1 byte
-        if len(data) == 1:
-            return self.datalink.st(address, data[0])
-        # Special case of 2 byte
-        if len(data) == 2:
-            self.datalink.st(address, data[0])
-            return self.datalink.st(address + 1, data[1])
-
-        # Range check
-        if len(data) > constants.UPDI_MAX_REPEAT_SIZE:
-            raise PymcuprogError("Invalid length")
-
-        # Store the address
-        self.datalink.st_ptr(address)
-
-        # Fire up the repeat
-        self.datalink.repeat(len(data))
-        return self.datalink.st_ptr_inc(data)
-*/
-  if (size == 1) {
-    return updi_link_st(pgm, address, buffer[0]);
-  }
-  if (size == 2) {
-    if (updi_link_st(pgm, address, buffer[0]) < 0) {
-      avrdude_message(MSG_INFO, "%s: ST operation failed\n", progname);
-      return -1;
-    }
-    return updi_link_st(pgm, address+1, buffer[1]);
-  }
-  if (size > UPDI_MAX_REPEAT_SIZE) {
-    avrdude_message(MSG_INFO, "%s: Invalid length\n", progname);
-    return -1;
-  }
-  if (updi_link_st_ptr(pgm, address) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
-    return -1;
-  }
-  if (updi_link_repeat(pgm, size) < 0) {
-    avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
-    return -1;
-  }
-  return updi_link_st_ptr_inc(pgm, buffer, size);
-}
-
-int updi_read_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
-{
-/*
-    def read_data_words(self, address, words):
-        """
-        Reads a number of words of data from UPDI
-
-        :param address: address to write to
-        :param words: number of words to read
-        """
-        self.logger.debug("Reading %d words from 0x%04X", words, address)
-
-        # Range check
-        if words > constants.UPDI_MAX_REPEAT_SIZE >> 1:
-            raise PymcuprogError("Cant read that many words in one go")
-
-        # Store the address
-        self.datalink.st_ptr(address)
-
-        # Fire up the repeat
-        if words > 1:
-            self.datalink.repeat(words)
-
-        # Do the read
-        return self.datalink.ld_ptr_inc16(words)
-*/
-  avrdude_message(MSG_DEBUG, "%s: Reading %d words from 0x%06X", progname, size, address);
-
-  if (size > (UPDI_MAX_REPEAT_SIZE >> 1)) {
-    avrdude_message(MSG_INFO, "%s: Can't read that many words in one go\n", progname);
-    return -1;
-  }
-
-  if (updi_link_st_ptr(pgm, address) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
-    return -1;
-  }
-
-  if (size > 1) {
-    if (updi_link_repeat(pgm, size) < 0) {
-      avrdude_message(MSG_INFO, "%s: Repeat operation failed\n", progname);
-      return -1;
-    }
-  }
-  return updi_link_ld_ptr_inc16(pgm, buffer, size);
-}
-
-int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size)
-{
-/*
-    def write_data_words(self, address, data):
-        """
-        Writes a number of words to memory
-
-        :param address: address to write to
-        :param data: data to write
-        """
-        # Special-case of 1 word
-        if len(data) == 2:
-            value = data[0] + (data[1] << 8)
-            return self.datalink.st16(address, value)
-
-        # Range check
-        if len(data) > constants.UPDI_MAX_REPEAT_SIZE << 1:
-            raise PymcuprogError("Invalid length")
-
-        # Store the address
-        self.datalink.st_ptr(address)
-
-        # Fire up the repeat
-        self.datalink.repeat(len(data) >> 1)
-        return self.datalink.st_ptr_inc16(data)
-*/
-  if (size == 2) {
-    return updi_link_st16(pgm, address, buffer[0] + (buffer[1] << 8));
-  }
-  if (size > UPDI_MAX_REPEAT_SIZE << 1) {
-    avrdude_message(MSG_INFO, "%s: Invalid length\n", progname);
-    return -1;
-  }
-  if (updi_link_st_ptr(pgm, address) < 0) {
-    avrdude_message(MSG_INFO, "%s: ST_PTR operation failed\n", progname);
-    return -1;
-  }
-  return updi_link_st_ptr_inc16_RSD(pgm, buffer, size >> 1, -1);
-}
diff --git a/avrdude/updi_readwrite.h b/avrdude/updi_readwrite.h
deleted file mode 100644
index 9519d179..00000000
--- a/avrdude/updi_readwrite.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#ifndef updi_readwrite_h
-#define updi_readwrite_h
-
-#include "libavrdude.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-int updi_read_cs(PROGRAMMER * pgm, uint8_t address, uint8_t * value);
-int updi_write_cs(PROGRAMMER * pgm, uint8_t address, uint8_t value);
-int updi_write_key(PROGRAMMER * pgm, unsigned char * buffer, uint8_t size_type, uint16_t size);
-int updi_read_sib(PROGRAMMER * pgm, unsigned char * buffer, uint16_t size);
-int updi_read_byte(PROGRAMMER * pgm, uint32_t address, uint8_t * value);
-int updi_write_byte(PROGRAMMER * pgm, uint32_t address, uint8_t value);
-int updi_read_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
-int updi_write_data(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
-int updi_read_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
-int updi_write_data_words(PROGRAMMER * pgm, uint32_t address, uint8_t * buffer, uint16_t size);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* updi_readwrite_h */
diff --git a/avrdude/updi_state.c b/avrdude/updi_state.c
deleted file mode 100644
index 63d80f46..00000000
--- a/avrdude/updi_state.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#include "ac_cfg.h"
-
-#include "libavrdude.h"
-#include "updi_state.h"
-
-updi_sib_info* updi_get_sib_info(PROGRAMMER * pgm)
-{
-  return &((updi_state *)(pgm->cookie))->sib_info;
-}
-
-updi_datalink_mode updi_get_datalink_mode(PROGRAMMER * pgm)
-{
-  return ((updi_state *)(pgm->cookie))->datalink_mode;
-}
-
-void updi_set_datalink_mode(PROGRAMMER * pgm, updi_datalink_mode mode)
-{
-  ((updi_state *)(pgm->cookie))->datalink_mode = mode;
-}
-
-updi_nvm_mode updi_get_nvm_mode(PROGRAMMER * pgm)
-{
-  return ((updi_state *)(pgm->cookie))->nvm_mode;
-}
-
-void updi_set_nvm_mode(PROGRAMMER * pgm, updi_nvm_mode mode)
-{
-  ((updi_state *)(pgm->cookie))->nvm_mode = mode;
-}
diff --git a/avrdude/updi_state.h b/avrdude/updi_state.h
deleted file mode 100644
index d4e39d4c..00000000
--- a/avrdude/updi_state.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * avrdude - A Downloader/Uploader for AVR device programmers
- * Copyright (C) 2021  Dawid Buchwald
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-/* $Id$ */
-
-/*
- * Based on pymcuprog
- * See https://github.com/microchip-pic-avr-tools/pymcuprog
- */
-
-#ifndef updi_state_h
-#define updi_state_h
-
-#include "libavrdude.h"
-
-typedef enum
-{
-  UPDI_LINK_MODE_16BIT,
-  UPDI_LINK_MODE_24BIT
-} updi_datalink_mode;
-
-typedef enum
-{
-  UPDI_NVM_MODE_V0,
-  UPDI_NVM_MODE_V2,
-  UPDI_NVM_MODE_V3
-} updi_nvm_mode;
-
-#define SIB_INFO_STRING_LENGTH 32
-#define SIB_INFO_FAMILY_LENGTH 8
-#define SIB_INFO_NVM_LENGTH    3
-#define SIB_INFO_DEBUG_LENGTH  3
-#define SIB_INFO_PDI_LENGTH    4
-#define SIB_INFO_EXTRA_LENGTH  20
-
-typedef struct 
-{
-  unsigned char sib_string[SIB_INFO_STRING_LENGTH+1];
-  char family_string[SIB_INFO_FAMILY_LENGTH+1];
-  char nvm_string[SIB_INFO_NVM_LENGTH+1];
-  char debug_string[SIB_INFO_DEBUG_LENGTH+1];
-  char pdi_string[SIB_INFO_PDI_LENGTH+1];
-  char extra_string[SIB_INFO_EXTRA_LENGTH+1];
-  char nvm_version;
-  char debug_version;
-} updi_sib_info;
-
-typedef struct
-{
-  updi_sib_info sib_info;
-  updi_datalink_mode datalink_mode;
-  updi_nvm_mode nvm_mode;
-} updi_state;
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-updi_sib_info* updi_get_sib_info(PROGRAMMER * pgm);
-updi_datalink_mode updi_get_datalink_mode(PROGRAMMER * pgm);
-void updi_set_datalink_mode(PROGRAMMER * pgm, updi_datalink_mode mode);
-updi_nvm_mode updi_get_nvm_mode(PROGRAMMER * pgm);
-void updi_set_nvm_mode(PROGRAMMER * pgm, updi_nvm_mode mode);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* updi_state_h */

From c093b21a67a98bba761941a5e19506233ded0aee Mon Sep 17 00:00:00 2001
From: Dawid Buchwald <dawid.buchwald@hotmail.com>
Date: Tue, 21 Dec 2021 21:45:36 +0100
Subject: [PATCH 12/12] Moved SerialUPDI sources to correct location

---
 serialupdi.c => src/serialupdi.c         | 0
 serialupdi.h => src/serialupdi.h         | 0
 updi_constants.h => src/updi_constants.h | 0
 updi_link.c => src/updi_link.c           | 0
 updi_link.h => src/updi_link.h           | 0
 updi_nvm.c => src/updi_nvm.c             | 0
 updi_nvm.h => src/updi_nvm.h             | 0
 updi_readwrite.c => src/updi_readwrite.c | 0
 updi_readwrite.h => src/updi_readwrite.h | 0
 updi_state.c => src/updi_state.c         | 0
 updi_state.h => src/updi_state.h         | 0
 11 files changed, 0 insertions(+), 0 deletions(-)
 rename serialupdi.c => src/serialupdi.c (100%)
 rename serialupdi.h => src/serialupdi.h (100%)
 rename updi_constants.h => src/updi_constants.h (100%)
 rename updi_link.c => src/updi_link.c (100%)
 rename updi_link.h => src/updi_link.h (100%)
 rename updi_nvm.c => src/updi_nvm.c (100%)
 rename updi_nvm.h => src/updi_nvm.h (100%)
 rename updi_readwrite.c => src/updi_readwrite.c (100%)
 rename updi_readwrite.h => src/updi_readwrite.h (100%)
 rename updi_state.c => src/updi_state.c (100%)
 rename updi_state.h => src/updi_state.h (100%)

diff --git a/serialupdi.c b/src/serialupdi.c
similarity index 100%
rename from serialupdi.c
rename to src/serialupdi.c
diff --git a/serialupdi.h b/src/serialupdi.h
similarity index 100%
rename from serialupdi.h
rename to src/serialupdi.h
diff --git a/updi_constants.h b/src/updi_constants.h
similarity index 100%
rename from updi_constants.h
rename to src/updi_constants.h
diff --git a/updi_link.c b/src/updi_link.c
similarity index 100%
rename from updi_link.c
rename to src/updi_link.c
diff --git a/updi_link.h b/src/updi_link.h
similarity index 100%
rename from updi_link.h
rename to src/updi_link.h
diff --git a/updi_nvm.c b/src/updi_nvm.c
similarity index 100%
rename from updi_nvm.c
rename to src/updi_nvm.c
diff --git a/updi_nvm.h b/src/updi_nvm.h
similarity index 100%
rename from updi_nvm.h
rename to src/updi_nvm.h
diff --git a/updi_readwrite.c b/src/updi_readwrite.c
similarity index 100%
rename from updi_readwrite.c
rename to src/updi_readwrite.c
diff --git a/updi_readwrite.h b/src/updi_readwrite.h
similarity index 100%
rename from updi_readwrite.h
rename to src/updi_readwrite.h
diff --git a/updi_state.c b/src/updi_state.c
similarity index 100%
rename from updi_state.c
rename to src/updi_state.c
diff --git a/updi_state.h b/src/updi_state.h
similarity index 100%
rename from updi_state.h
rename to src/updi_state.h