From 0a335e2f73ac24d14ab4df731712346aa0922883 Mon Sep 17 00:00:00 2001 From: MCUdude Date: Tue, 21 Dec 2021 22:10:51 +0100 Subject: [PATCH 1/7] Add jtag2updi programmer --- src/avrdude.conf.in | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index 523137fd..0e4b451c 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -1647,6 +1647,17 @@ programmer miso = ~8; ; +# JTAG2UPDI +# https://github.com/ElTangas/jtag2updi + +programmer + id = "jtag2updi"; + desc = "JTAGv2 to UPDI bridge"; + type = "jtagmkii_pdi"; + connection_type = serial; + baudrate = 115200; +; + # # PART DEFINITIONS # From 71d5dbec48d9ec132aca7a85187191623ac6ef9e Mon Sep 17 00:00:00 2001 From: MCUdude Date: Tue, 21 Dec 2021 23:22:06 +0100 Subject: [PATCH 2/7] Add more jtagmkii baud rates --- src/jtagmkII.c | 41 ++++++++++++++++++++++++++++++++--------- src/jtagmkII_private.h | 23 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/jtagmkII.c b/src/jtagmkII.c index be6a4ed6..38eba40d 100644 --- a/src/jtagmkII.c +++ b/src/jtagmkII.c @@ -1254,15 +1254,38 @@ static unsigned char jtagmkII_get_baud(long baud) long baud; unsigned char val; } baudtab[] = { - { 2400L, PAR_BAUD_2400 }, - { 4800L, PAR_BAUD_4800 }, - { 9600L, PAR_BAUD_9600 }, - { 19200L, PAR_BAUD_19200 }, - { 38400L, PAR_BAUD_38400 }, - { 57600L, PAR_BAUD_57600 }, - { 115200L, PAR_BAUD_115200 }, - { 14400L, PAR_BAUD_14400 }, - }; + { 2400L, PAR_BAUD_2400 }, + { 4800L, PAR_BAUD_4800 }, + { 9600L, PAR_BAUD_9600 }, + { 19200L, PAR_BAUD_19200 }, + { 38400L, PAR_BAUD_38400 }, + { 57600L, PAR_BAUD_57600 }, + { 115200L, PAR_BAUD_115200 }, + { 14400L, PAR_BAUD_14400 }, + /* Extension to jtagmkII protocol: extra baud rates, standard series. */ + { 153600L, PAR_BAUD_153600 }, + { 230400L, PAR_BAUD_230400 }, + { 460800L, PAR_BAUD_460800 }, + { 921600L, PAR_BAUD_921600 }, + /* Extension to jtagmkII protocol: extra baud rates, binary series. */ + { 128000L, PAR_BAUD_128000 }, + { 256000L, PAR_BAUD_256000 }, + { 512000L, PAR_BAUD_512000 }, + { 1024000L, PAR_BAUD_1024000 }, + /* Extension to jtagmkII protocol: extra baud rates, decimal series. */ + { 150000L, PAR_BAUD_150000 }, + { 200000L, PAR_BAUD_200000 }, + { 250000L, PAR_BAUD_250000 }, + { 300000L, PAR_BAUD_300000 }, + { 400000L, PAR_BAUD_400000 }, + { 500000L, PAR_BAUD_500000 }, + { 600000L, PAR_BAUD_600000 }, + { 666666L, PAR_BAUD_666666 }, + { 1000000L, PAR_BAUD_1000000 }, + { 1500000L, PAR_BAUD_1500000 }, + { 2000000L, PAR_BAUD_2000000 }, + { 3000000L, PAR_BAUD_3000000 }, +}; int i; for (i = 0; i < sizeof baudtab / sizeof baudtab[0]; i++) diff --git a/src/jtagmkII_private.h b/src/jtagmkII_private.h index 6df8f6f2..14860854 100644 --- a/src/jtagmkII_private.h +++ b/src/jtagmkII_private.h @@ -206,6 +206,29 @@ # define PAR_BAUD_57600 0x06 # define PAR_BAUD_115200 0x07 # define PAR_BAUD_14400 0x08 +/* Extension to jtagmkII protocol: extra baud rates, standard series. */ +# define PAR_BAUD_153600 0x09 +# define PAR_BAUD_230400 0x0A +# define PAR_BAUD_460800 0x0B +# define PAR_BAUD_921600 0x0C +/* Extension to jtagmkII protocol: extra baud rates, binary series. */ +# define PAR_BAUD_128000 0x0D +# define PAR_BAUD_256000 0x0E +# define PAR_BAUD_512000 0x0F +# define PAR_BAUD_1024000 0x10 +/* Extension to jtagmkII protocol: extra baud rates, decimal series. */ +# define PAR_BAUD_150000 0x11 +# define PAR_BAUD_200000 0x12 +# define PAR_BAUD_250000 0x13 +# define PAR_BAUD_300000 0x14 +# define PAR_BAUD_400000 0x15 +# define PAR_BAUD_500000 0x16 +# define PAR_BAUD_600000 0x17 +# define PAR_BAUD_666666 0x18 +# define PAR_BAUD_1000000 0x19 +# define PAR_BAUD_1500000 0x1A +# define PAR_BAUD_2000000 0x1B +# define PAR_BAUD_3000000 0x1C #define PAR_OCD_VTARGET 0x06 #define PAR_OCD_JTAG_CLK 0x07 #define PAR_OCD_BREAK_CAUSE 0x08 From a2a276a8cc42d85267c1d1733d512975101b799b Mon Sep 17 00:00:00 2001 From: MCUdude Date: Wed, 29 Dec 2021 13:32:56 +0100 Subject: [PATCH 3/7] Add support for UPDI devices though jtag2updi 'Hack' borrowed from https://github.com/facchinm/avrdude --- src/jtagmkII.c | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/jtagmkII.c b/src/jtagmkII.c index 38eba40d..263a1f6b 100644 --- a/src/jtagmkII.c +++ b/src/jtagmkII.c @@ -891,7 +891,7 @@ static int jtagmkII_chip_erase(PROGRAMMER * pgm, AVRPART * p) int status, len; unsigned char buf[6], *resp, c; - if (p->flags & AVRPART_HAS_PDI) { + if (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI)) { buf[0] = CMND_XMEGA_ERASE; buf[1] = XMEGA_ERASE_CHIP; memset(buf + 2, 0, 4); /* address of area to be erased */ @@ -902,7 +902,7 @@ static int jtagmkII_chip_erase(PROGRAMMER * pgm, AVRPART * p) } avrdude_message(MSG_NOTICE2, "%s: jtagmkII_chip_erase(): Sending %schip erase command: ", progname, - (p->flags & AVRPART_HAS_PDI)? "Xmega ": ""); + (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))? "Xmega ": ""); jtagmkII_send(pgm, buf, len); status = jtagmkII_recv(pgm, &resp); @@ -928,7 +928,7 @@ static int jtagmkII_chip_erase(PROGRAMMER * pgm, AVRPART * p) return -1; } - if (!(p->flags & AVRPART_HAS_PDI)) + if (!(p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))) pgm->initialize(pgm, p); return 0; @@ -986,7 +986,7 @@ static void jtagmkII_set_devdescr(PROGRAMMER * pgm, AVRPART * p) } } sendbuf.dd.ucCacheType = - (p->flags & AVRPART_HAS_PDI)? 0x02 /* ATxmega */: 0x00; + (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))? 0x02 /* ATxmega */: 0x00; avrdude_message(MSG_NOTICE2, "%s: jtagmkII_set_devdescr(): " "Sending set device descriptor command: ", @@ -1312,7 +1312,7 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p) ok = 1; } else if (pgm->flag & PGM_FL_IS_PDI) { ifname = "PDI"; - if (p->flags & AVRPART_HAS_PDI) + if (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI)) ok = 1; } else { ifname = "JTAG"; @@ -1358,20 +1358,20 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p) * mode from JTAG to JTAG_XMEGA. */ if ((pgm->flag & PGM_FL_IS_JTAG) && - (p->flags & AVRPART_HAS_PDI)) { + (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))) { if (jtagmkII_getsync(pgm, EMULATOR_MODE_JTAG_XMEGA) < 0) return -1; } /* * Must set the device descriptor before entering programming mode. */ - if (PDATA(pgm)->fwver >= 0x700 && (p->flags & AVRPART_HAS_PDI) != 0) + if (PDATA(pgm)->fwver >= 0x700 && (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI)) != 0) jtagmkII_set_xmega_params(pgm, p); else jtagmkII_set_devdescr(pgm, p); PDATA(pgm)->boot_start = ULONG_MAX; - if ((p->flags & AVRPART_HAS_PDI)) { + if ((p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))) { /* * Find out where the border between application and boot area * is. @@ -1411,7 +1411,7 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p) } PDATA(pgm)->flash_pageaddr = PDATA(pgm)->eeprom_pageaddr = (unsigned long)-1L; - if (PDATA(pgm)->fwver >= 0x700 && (p->flags & AVRPART_HAS_PDI)) { + if (PDATA(pgm)->fwver >= 0x700 && (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))) { /* * Work around for * https://savannah.nongnu.org/bugs/index.php?37942 @@ -1428,7 +1428,7 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p) return -1; } - if ((pgm->flag & PGM_FL_IS_JTAG) && !(p->flags & AVRPART_HAS_PDI)) { + if ((pgm->flag & PGM_FL_IS_JTAG) && !(p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))) { strcpy(hfuse.desc, "hfuse"); if (jtagmkII_read_byte(pgm, p, &hfuse, 1, &b) < 0) return -1; @@ -1902,7 +1902,7 @@ static int jtagmkII_page_erase(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, avrdude_message(MSG_NOTICE2, "%s: jtagmkII_page_erase(.., %s, 0x%x)\n", progname, m->desc, addr); - if (!(p->flags & AVRPART_HAS_PDI)) { + if (!(p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI))) { avrdude_message(MSG_INFO, "%s: jtagmkII_page_erase: not an Xmega device\n", progname); return -1; @@ -2016,7 +2016,7 @@ static int jtagmkII_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, if (strcmp(m->desc, "flash") == 0) { PDATA(pgm)->flash_pageaddr = (unsigned long)-1L; cmd[1] = jtagmkII_memtype(pgm, p, addr); - if (p->flags & AVRPART_HAS_PDI) + if (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI)) /* dynamically decide between flash/boot memtype */ dynamic_memtype = 1; } else if (strcmp(m->desc, "eeprom") == 0) { @@ -2035,18 +2035,18 @@ static int jtagmkII_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, free(cmd); return n_bytes; } - cmd[1] = ( p->flags & AVRPART_HAS_PDI ) ? MTYPE_EEPROM : MTYPE_EEPROM_PAGE; + cmd[1] = ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) ? MTYPE_EEPROM : MTYPE_EEPROM_PAGE; PDATA(pgm)->eeprom_pageaddr = (unsigned long)-1L; } else if ( ( strcmp(m->desc, "usersig") == 0 ) ) { cmd[1] = MTYPE_USERSIG; } else if ( ( strcmp(m->desc, "boot") == 0 ) ) { cmd[1] = MTYPE_BOOT_FLASH; - } else if ( p->flags & AVRPART_HAS_PDI ) { + } else if ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) { cmd[1] = MTYPE_FLASH; } else { cmd[1] = MTYPE_SPM; } - serial_recv_timeout = 100; + serial_recv_timeout = 200; for (; addr < maxaddr; addr += page_size) { if ((maxaddr - addr) < page_size) block_size = maxaddr - addr; @@ -2143,11 +2143,11 @@ static int jtagmkII_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, cmd[0] = CMND_READ_MEMORY; if (strcmp(m->desc, "flash") == 0) { cmd[1] = jtagmkII_memtype(pgm, p, addr); - if (p->flags & AVRPART_HAS_PDI) + if (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI)) /* dynamically decide between flash/boot memtype */ dynamic_memtype = 1; } else if (strcmp(m->desc, "eeprom") == 0) { - cmd[1] = ( p->flags & AVRPART_HAS_PDI ) ? MTYPE_EEPROM : MTYPE_EEPROM_PAGE; + cmd[1] = ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) ? MTYPE_EEPROM : MTYPE_EEPROM_PAGE; if (pgm->flag & PGM_FL_IS_DW) return -1; } else if ( ( strcmp(m->desc, "prodsig") == 0 ) ) { @@ -2156,7 +2156,7 @@ static int jtagmkII_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, cmd[1] = MTYPE_USERSIG; } else if ( ( strcmp(m->desc, "boot") == 0 ) ) { cmd[1] = MTYPE_BOOT_FLASH; - } else if ( p->flags & AVRPART_HAS_PDI ) { + } else if ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) { cmd[1] = MTYPE_FLASH; } else { cmd[1] = MTYPE_SPM; @@ -2241,7 +2241,7 @@ static int jtagmkII_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, unsupp = 0; addr += mem->offset; - cmd[1] = ( p->flags & AVRPART_HAS_PDI ) ? MTYPE_FLASH : MTYPE_FLASH_PAGE; + cmd[1] = ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) ? MTYPE_FLASH : MTYPE_FLASH_PAGE; if (strcmp(mem->desc, "flash") == 0 || strcmp(mem->desc, "application") == 0 || strcmp(mem->desc, "apptable") == 0 || @@ -2251,7 +2251,7 @@ static int jtagmkII_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, paddr_ptr = &PDATA(pgm)->flash_pageaddr; cache_ptr = PDATA(pgm)->flash_pagecache; } else if (strcmp(mem->desc, "eeprom") == 0) { - if ( (pgm->flag & PGM_FL_IS_DW) || ( p->flags & AVRPART_HAS_PDI ) ) { + if ( (pgm->flag & PGM_FL_IS_DW) || ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) ) { /* debugWire cannot use page access for EEPROM */ cmd[1] = MTYPE_EEPROM; } else { @@ -2417,7 +2417,7 @@ static int jtagmkII_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, writedata = data; cmd[0] = CMND_WRITE_MEMORY; - cmd[1] = ( p->flags & AVRPART_HAS_PDI ) ? MTYPE_FLASH : MTYPE_SPM; + cmd[1] = ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) ? MTYPE_FLASH : MTYPE_SPM; if (strcmp(mem->desc, "flash") == 0) { if ((addr & 1) == 1) { /* odd address = high byte */ @@ -2431,7 +2431,7 @@ static int jtagmkII_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (pgm->flag & PGM_FL_IS_DW) unsupp = 1; } else if (strcmp(mem->desc, "eeprom") == 0) { - cmd[1] = ( p->flags & AVRPART_HAS_PDI ) ? MTYPE_EEPROM_XMEGA: MTYPE_EEPROM; + cmd[1] = ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) ? MTYPE_EEPROM_XMEGA: MTYPE_EEPROM; need_progmode = 0; PDATA(pgm)->eeprom_pageaddr = (unsigned long)-1L; } else if (strcmp(mem->desc, "lfuse") == 0) { @@ -2736,7 +2736,7 @@ static void jtagmkII_print_parms(PROGRAMMER * pgm) static unsigned char jtagmkII_memtype(PROGRAMMER * pgm, AVRPART * p, unsigned long addr) { - if ( p->flags & AVRPART_HAS_PDI ) { + if ( p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI) ) { if (addr >= PDATA(pgm)->boot_start) return MTYPE_BOOT_FLASH; else @@ -2752,7 +2752,7 @@ static unsigned int jtagmkII_memaddr(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, * Xmega devices handled by V7+ firmware don't want to be told their * m->offset within the write memory command. */ - if (PDATA(pgm)->fwver >= 0x700 && (p->flags & AVRPART_HAS_PDI) != 0) { + if (PDATA(pgm)->fwver >= 0x700 && (p->flags & (AVRPART_HAS_PDI | AVRPART_HAS_UPDI)) != 0) { if (addr >= PDATA(pgm)->boot_start) /* * all memories but "flash" are smaller than boot_start anyway, so @@ -4047,4 +4047,3 @@ void jtagmkII_dragon_pdi_initpgm(PROGRAMMER * pgm) pgm->page_size = 256; pgm->flag = PGM_FL_IS_PDI; } - From f5bec43812875c59ba9e504d8ced05f50f509a1a Mon Sep 17 00:00:00 2001 From: MCUdude Date: Wed, 29 Dec 2021 14:25:09 +0100 Subject: [PATCH 4/7] Add jtag2updi programmer to docs --- src/avrdude.1 | 7 +++++++ src/doc/avrdude.texi | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/avrdude.1 b/src/avrdude.1 index 940cd499..6c33f6d1 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -233,6 +233,13 @@ In a nutshell, this programmer consists of simple USB->UART adapter, diode and couple of resistors. It uses serial connection to provide UPDI interface. See the texinfo documentation for more details and known issues. .Pp +The jtag2updi programmer is supported, +and can program AVRs with a UPDI interface. +Jtag2updi is just a firmware that can be uploaded to an AVR, +which enables it to interface with avrdude using the jtagice mkii protocol +via a serial link. +.Li https://github.com/ElTangas/jtag2updi +.Pp Input files can be provided, and output files can be written in different file formats, such as raw binary files containing the data to download to the chip, Intel hex format, or Motorola S-record diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index c45f7e26..13008f33 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -315,6 +315,12 @@ In a nutshell, this programmer consists of simple USB->UART adapter, diode and couple of resistors. It uses serial connection to provide UPDI interface. @xref{SerialUPDI programmer} for more details and known issues. +The jtag2updi programmer is supported, +and can program AVRs with a UPDI interface. +Jtag2updi is just a firmware that can be uploaded to an AVR, +which enables it to interface with avrdude using the jtagice mkii protocol +via a serial link (@url{https://github.com/ElTangas/jtag2updi}). + @menu * History:: @end menu From b9f03b1377ceed439fe7422d752145a1555a6afd Mon Sep 17 00:00:00 2001 From: MCUdude Date: Wed, 29 Dec 2021 14:25:36 +0100 Subject: [PATCH 5/7] Fix typo in URL --- src/avrdude.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/avrdude.1 b/src/avrdude.1 index 6c33f6d1..f7f5bce2 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -228,7 +228,7 @@ utility, but it also contains some performance improvements included in Spence Kohde's .Em DxCore Arduino core -.Li https://github.com/SpenceKonde/DCore . +.Li https://github.com/SpenceKonde/DxCore . In a nutshell, this programmer consists of simple USB->UART adapter, diode and couple of resistors. It uses serial connection to provide UPDI interface. See the texinfo documentation for more details and known issues. From dcd5374ae932372ed5f79dce39d85190053cbc38 Mon Sep 17 00:00:00 2001 From: MCUdude Date: Sun, 2 Jan 2022 12:57:42 +0100 Subject: [PATCH 6/7] Print meaningful error if programmer doesn't support target --- src/jtagmkII.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/jtagmkII.c b/src/jtagmkII.c index 263a1f6b..f41d126b 100644 --- a/src/jtagmkII.c +++ b/src/jtagmkII.c @@ -1305,6 +1305,14 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p) int ok; const char *ifname; + /* Abort and print error if programmer does not support the target microcontroller */ + if ((strncmp(ldata(lfirst(pgm->id)), "jtag2updi", strlen("jtag2updi")) == 0 && p->flags & AVRPART_HAS_PDI) || + (strncmp(ldata(lfirst(pgm->id)), "jtagmkII", strlen("jtagmkII")) == 0 && p->flags & AVRPART_HAS_UPDI)) { + avrdude_message(MSG_INFO, "Error: programmer %s does not support target %s\n\n", + ldata(lfirst(pgm->id)), p->desc); + return -1; + } + ok = 0; if (pgm->flag & PGM_FL_IS_DW) { ifname = "debugWire"; From f96b98e9dfb99e422eadef71c97b44d60dceeb35 Mon Sep 17 00:00:00 2001 From: MCUdude Date: Sun, 2 Jan 2022 19:20:05 +0100 Subject: [PATCH 7/7] Mute "flash and boot" warning if s UPDI programmer is used Currently, no UPDI compatible AVR has a dedicated boot section like the Xmegas do --- src/jtagmkII.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/jtagmkII.c b/src/jtagmkII.c index f41d126b..a98fdf2a 100644 --- a/src/jtagmkII.c +++ b/src/jtagmkII.c @@ -1387,8 +1387,10 @@ static int jtagmkII_initialize(PROGRAMMER * pgm, AVRPART * p) AVRMEM *bootmem = avr_locate_mem(p, "boot"); AVRMEM *flashmem = avr_locate_mem(p, "flash"); if (bootmem == NULL || flashmem == NULL) { - avrdude_message(MSG_INFO, "%s: jtagmkII_initialize(): Cannot locate \"flash\" and \"boot\" memories in description\n", - progname); + if (strncmp(ldata(lfirst(pgm->id)), "jtagmkII", strlen("jtagmkII")) == 0) { + avrdude_message(MSG_INFO, "%s: jtagmkII_initialize(): Cannot locate \"flash\" and \"boot\" memories in description\n", + progname); + } } else { if (PDATA(pgm)->fwver < 0x700) { /* V7+ firmware does not need this anymore */