Submitted by Darell Tan:

patch #7244: TPI bitbang implementation
* bitbang.c: Add TPI bitbang stuff.
* bitbang.h: (Ditto.)
* avr.c: (Ditto.)
* avr.h: (Ditto.)
* pgm.c: (Ditto.)
* pgm.h: (Ditto.)
* serbb_posix.c: Wire bitbang_cmd_tpi into the struct pgm.
* serbb_win32.c: (Ditto.)
* par.c: (Ditto.)
* doc/avrdude.texi: Document the TPI bitbang support.



git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@976 81a1dc3b-b13d-400b-aceb-764788c761c2
This commit is contained in:
joerg_wunsch 2011-08-23 21:03:36 +00:00
parent 3a44cb66ef
commit 6431eece98
13 changed files with 466 additions and 1 deletions

View File

@ -15,6 +15,7 @@ Contributors:
Thomas Fischl <tfischl@gmx.de>
David Hoerl <dhoerl@mac.com>
Michal Ludvig <mludvig@logix.net.nz>
Darell Tan
For minor contributions, please see the ChangeLog files.

View File

@ -1,3 +1,18 @@
2011-08-23 Joerg Wunsch <j.gnu@uriah.heep.sax.de>
Submitted by Darell Tan:
patch #7244: TPI bitbang implementation
* bitbang.c: Add TPI bitbang stuff.
* bitbang.h: (Ditto.)
* avr.c: (Ditto.)
* avr.h: (Ditto.)
* pgm.c: (Ditto.)
* pgm.h: (Ditto.)
* serbb_posix.c: Wire bitbang_cmd_tpi into the struct pgm.
* serbb_win32.c: (Ditto.)
* par.c: (Ditto.)
* doc/avrdude.texi: Document the TPI bitbang support.
2011-08-17 Joerg Wunsch <j.gnu@uriah.heep.sax.de>
Submitted by Grygoriy Fuchedzhy:

2
NEWS
View File

@ -13,6 +13,8 @@ Current:
- ATtiny4313
* New programmers supported:
- TPI programming through bitbang programmers (both, serial
and parallel ones)
* Bugfixes

175
avr.c
View File

@ -1,6 +1,7 @@
/*
* avrdude - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2000-2004 Brian S. Dean <bsd@bsdhome.com>
* Copyright 2011 Darell Tan
*
* 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
@ -36,17 +37,61 @@
#include "ppi.h"
#include "safemode.h"
#include "update.h"
#include "tpi.h"
FP_UpdateProgress update_progress;
#define DEBUG 0
/* TPI: returns 1 if NVM controller busy, 0 if free */
int avr_tpi_poll_nvmbsy(PROGRAMMER *pgm)
{
unsigned char cmd;
unsigned char res;
int rc = 0;
cmd = TPI_CMD_SIN | TPI_SIO_ADDR(TPI_IOREG_NVMCSR);
rc = pgm->cmd_tpi(pgm, &cmd, 1, &res, 1);
return (rc & TPI_IOREG_NVMCSR_NVMBSY);
}
/* TPI: setup NVMCMD register and pointer register (PR) for read/write/erase */
static int avr_tpi_setup_rw(PROGRAMMER * pgm, AVRMEM * mem,
unsigned long addr, unsigned char nvmcmd)
{
unsigned char cmd[4];
int rc;
/* set NVMCMD register */
cmd[0] = TPI_CMD_SOUT | TPI_SIO_ADDR(TPI_IOREG_NVMCMD);
cmd[1] = nvmcmd;
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
if (rc == -1)
return -1;
/* set Pointer Register (PR) */
cmd[0] = TPI_CMD_SSTPR | 0;
cmd[1] = (mem->offset + addr) & 0xFF;
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
if (rc == -1)
return -1;
cmd[0] = TPI_CMD_SSTPR | 1;
cmd[1] = ((mem->offset + addr) >> 8) & 0xFF;
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
if (rc == -1)
return -1;
return 0;
}
int avr_read_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
unsigned long addr, unsigned char * value)
{
unsigned char cmd[4];
unsigned char res[4];
unsigned char data;
int r;
OPCODE * readop, * lext;
if (pgm->cmd == NULL) {
@ -60,6 +105,27 @@ int avr_read_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
pgm->pgm_led(pgm, ON);
pgm->err_led(pgm, OFF);
if (p->flags & AVRPART_HAS_TPI) {
if (pgm->cmd_tpi == NULL) {
fprintf(stderr, "%s: Error: %s programmer does not support TPI\n",
progname, pgm->type);
return -1;
}
while (avr_tpi_poll_nvmbsy(pgm));
/* setup for read */
avr_tpi_setup_rw(pgm, mem, addr, TPI_NVMCMD_NO_OPERATION);
/* load byte */
cmd[0] = TPI_CMD_SLD;
r = pgm->cmd_tpi(pgm, cmd, 1, value, 1);
if (r == -1)
return -1;
return 0;
}
/*
* figure out what opcode to use
*/
@ -151,6 +217,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size,
unsigned char rbyte;
unsigned long i;
unsigned char * buf;
unsigned char cmd[4];
AVRMEM * mem;
int rc;
@ -171,6 +238,33 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size,
*/
memset(buf, 0xff, size);
/* supports "paged load" thru post-increment */
if ((p->flags & AVRPART_HAS_TPI) && mem->page_size != 0) {
if (pgm->cmd_tpi == NULL) {
fprintf(stderr, "%s: Error: %s programmer does not support TPI\n",
progname, pgm->type);
return -1;
}
while (avr_tpi_poll_nvmbsy(pgm));
/* setup for read (NOOP) */
avr_tpi_setup_rw(pgm, mem, 0, TPI_NVMCMD_NO_OPERATION);
/* load bytes */
for (i = 0; i < size; i++) {
cmd[0] = TPI_CMD_SLD_PI;
rc = pgm->cmd_tpi(pgm, cmd, 1, &buf[i], 1);
if (rc == -1) {
fprintf(stderr, "avr_read(): error reading address 0x%04lx\n", i);
return -1;
}
report_progress(i, size, NULL);
}
return avr_mem_hiaddr(mem);
}
if (pgm->paged_load != NULL && mem->page_size != 0) {
/*
* the programmer supports a paged mode read, perhaps more
@ -303,6 +397,52 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
return -1;
}
if (p->flags & AVRPART_HAS_TPI) {
if (pgm->cmd_tpi == NULL) {
fprintf(stderr, "%s: Error: %s programmer does not support TPI\n",
progname, pgm->type);
return -1;
}
if (strcmp(mem->desc, "flash") == 0) {
fprintf(stderr, "Writing a byte to flash is not supported for %s\n", p->desc);
return -1;
} else if ((mem->offset + addr) & 1) {
fprintf(stderr, "Writing a byte to an odd location is not supported for %s\n", p->desc);
return -1;
}
while (avr_tpi_poll_nvmbsy(pgm));
/* must erase fuse first */
if (strcmp(mem->desc, "fuse") == 0) {
/* setup for SECTION_ERASE (high byte) */
avr_tpi_setup_rw(pgm, mem, addr | 1, TPI_NVMCMD_SECTION_ERASE);
/* write dummy byte */
cmd[0] = TPI_CMD_SST;
cmd[1] = 0xFF;
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
while (avr_tpi_poll_nvmbsy(pgm));
}
/* setup for WORD_WRITE */
avr_tpi_setup_rw(pgm, mem, addr, TPI_NVMCMD_WORD_WRITE);
cmd[0] = TPI_CMD_SST_PI;
cmd[1] = data;
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
/* dummy high byte to start WORD_WRITE */
cmd[0] = TPI_CMD_SST_PI;
cmd[1] = data;
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
while (avr_tpi_poll_nvmbsy(pgm));
return 0;
}
if (!mem->paged) {
/*
* check to see if the write is necessary by reading the existing
@ -540,6 +680,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size,
long i;
unsigned char data;
int werror;
unsigned char cmd[4];
AVRMEM * m;
m = avr_locate_mem(p, memtype);
@ -566,6 +707,40 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size,
progbuf, wsize);
}
if ((p->flags & AVRPART_HAS_TPI) && m->page_size != 0) {
if (pgm->cmd_tpi == NULL) {
fprintf(stderr,
"%s: Error: %s programmer does not support TPI\n",
progname, pgm->type);
return -1;
}
while (avr_tpi_poll_nvmbsy(pgm));
/* setup for WORD_WRITE */
avr_tpi_setup_rw(pgm, m, 0, TPI_NVMCMD_WORD_WRITE);
/* make sure it's aligned to a word boundary */
if (wsize & 0x1) {
wsize++;
}
/* write words, low byte first */
for (i = 0; i < wsize; i++) {
cmd[0] = TPI_CMD_SST_PI;
cmd[1] = m->buf[i];
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
cmd[1] = m->buf[++i];
rc = pgm->cmd_tpi(pgm, cmd, 2, NULL, 0);
while (avr_tpi_poll_nvmbsy(pgm));
report_progress(i, wsize, NULL);
}
return i;
}
if (pgm->paged_write != NULL && m->page_size != 0) {
/*
* the programmer supports a paged mode write, perhaps more

1
avr.h
View File

@ -37,6 +37,7 @@ extern FP_UpdateProgress update_progress;
extern "C" {
#endif
int avr_tpi_poll_nvmbsy(PROGRAMMER *pgm);
int avr_read_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem,
unsigned long addr, unsigned char * value);

226
bitbang.c
View File

@ -2,6 +2,7 @@
* avrdude - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2000, 2001, 2002, 2003 Brian S. Dean <bsd@bsdhome.com>
* Copyright (C) 2005 Michael Holzt <kju-avr@fqdn.org>
* Copyright 2011 Darell Tan
*
* 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
@ -39,6 +40,7 @@
#include "pgm.h"
#include "par.h"
#include "serbb.h"
#include "tpi.h"
static int delay_decrement;
@ -212,6 +214,93 @@ static unsigned char bitbang_txrx(PROGRAMMER * pgm, unsigned char byte)
return rbyte;
}
static int bitbang_tpi_clk(PROGRAMMER * pgm)
{
unsigned char r = 0;
pgm->setpin(pgm, pgm->pinno[PIN_AVR_SCK], 1);
r = pgm->getpin(pgm, pgm->pinno[PIN_AVR_MISO]);
pgm->setpin(pgm, pgm->pinno[PIN_AVR_SCK], 0);
return r;
}
void bitbang_tpi_tx(PROGRAMMER * pgm, unsigned char byte)
{
int i;
unsigned char b, parity;
/* start bit */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], 0);
bitbang_tpi_clk(pgm);
parity = 0;
for (i = 0; i <= 7; i++) {
b = (byte >> i) & 0x01;
parity ^= b;
/* set the data input line as desired */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], b);
bitbang_tpi_clk(pgm);
}
/* parity bit */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], parity);
bitbang_tpi_clk(pgm);
/* 2 stop bits */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], 1);
bitbang_tpi_clk(pgm);
bitbang_tpi_clk(pgm);
}
int bitbang_tpi_rx(PROGRAMMER * pgm)
{
int i;
unsigned char b, rbyte, parity;
/* make sure pin is on for "pullup" */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], 1);
/* wait for start bit (up to 10 bits) */
b = 1;
for (i = 0; i < 10; i++) {
b = bitbang_tpi_clk(pgm);
if (b == 0)
break;
}
if (b != 0) {
fprintf(stderr, "bitbang_tpi_rx: start bit not received correctly\n");
return -1;
}
rbyte = 0;
parity = 0;
for (i=0; i<=7; i++) {
b = bitbang_tpi_clk(pgm);
parity ^= b;
rbyte |= b << i;
}
/* parity bit */
if (bitbang_tpi_clk(pgm) != parity) {
fprintf(stderr, "bitbang_tpi_rx: parity bit is wrong\n");
return -1;
}
/* 2 stop bits */
b = 1;
b &= bitbang_tpi_clk(pgm);
b &= bitbang_tpi_clk(pgm);
if (b != 1) {
fprintf(stderr, "bitbang_tpi_rx: stop bits not received correctly\n");
return -1;
}
return rbyte;
}
int bitbang_rdy_led(PROGRAMMER * pgm, int value)
{
@ -267,6 +356,44 @@ int bitbang_cmd(PROGRAMMER * pgm, unsigned char cmd[4],
return 0;
}
int bitbang_cmd_tpi(PROGRAMMER * pgm, unsigned char cmd[],
int cmd_len, unsigned char res[], int res_len)
{
int i, r;
pgm->pgm_led(pgm, ON);
for (i=0; i<cmd_len; i++) {
bitbang_tpi_tx(pgm, cmd[i]);
}
r = 0;
for (i=0; i<res_len; i++) {
r = bitbang_tpi_rx(pgm);
if (r == -1)
break;
res[i] = r;
}
if(verbose >= 2)
{
fprintf(stderr, "bitbang_cmd_tpi(): [ ");
for(i = 0; i < cmd_len; i++)
fprintf(stderr, "%02X ", cmd[i]);
fprintf(stderr, "] [ ");
for(i = 0; i < res_len; i++)
{
fprintf(stderr, "%02X ", res[i]);
}
fprintf(stderr, "]\n");
}
pgm->pgm_led(pgm, OFF);
if (r == -1)
return -1;
return 0;
}
/*
* transmit bytes via SPI and return the results; 'cmd' and
* 'res' must point to data buffers
@ -308,6 +435,39 @@ int bitbang_chip_erase(PROGRAMMER * pgm, AVRPART * p)
{
unsigned char cmd[4];
unsigned char res[4];
AVRMEM *mem;
if (p->flags & AVRPART_HAS_TPI) {
pgm->pgm_led(pgm, ON);
while (avr_tpi_poll_nvmbsy(pgm));
/* NVMCMD <- CHIP_ERASE */
bitbang_tpi_tx(pgm, TPI_CMD_SOUT | TPI_SIO_ADDR(TPI_IOREG_NVMCMD));
bitbang_tpi_tx(pgm, TPI_NVMCMD_CHIP_ERASE); /* CHIP_ERASE */
/* Set Pointer Register */
mem = avr_locate_mem(p, "flash");
if (mem == NULL) {
fprintf(stderr, "No flash memory to erase for part %s\n",
p->desc);
return -1;
}
bitbang_tpi_tx(pgm, TPI_CMD_SSTPR | 0);
bitbang_tpi_tx(pgm, (mem->offset & 0xFF) | 1); /* high byte */
bitbang_tpi_tx(pgm, TPI_CMD_SSTPR | 1);
bitbang_tpi_tx(pgm, (mem->offset >> 8) & 0xFF);
/* write dummy value to start erase */
bitbang_tpi_tx(pgm, TPI_CMD_SST);
bitbang_tpi_tx(pgm, 0xFF);
while (avr_tpi_poll_nvmbsy(pgm));
pgm->pgm_led(pgm, OFF);
return 0;
}
if (p->op[AVR_OP_CHIP_ERASE] == NULL) {
fprintf(stderr, "chip erase instruction not defined for part \"%s\"\n",
@ -336,6 +496,19 @@ int bitbang_program_enable(PROGRAMMER * pgm, AVRPART * p)
{
unsigned char cmd[4];
unsigned char res[4];
int i;
if (p->flags & AVRPART_HAS_TPI) {
/* enable NVM programming */
bitbang_tpi_tx(pgm, TPI_CMD_SKEY);
for (i = sizeof(tpi_skey) - 1; i >= 0; i--)
bitbang_tpi_tx(pgm, tpi_skey[i]);
/* check NVMEN bit */
bitbang_tpi_tx(pgm, TPI_CMD_SLDCS | TPI_REG_TPISR);
i = bitbang_tpi_rx(pgm);
return (i != -1 && (i & TPI_REG_TPISR_NVMEN)) ? 0 : -2;
}
if (p->op[AVR_OP_PGM_ENABLE] == NULL) {
fprintf(stderr, "program enable instruction not defined for part \"%s\"\n",
@ -360,17 +533,68 @@ int bitbang_initialize(PROGRAMMER * pgm, AVRPART * p)
{
int rc;
int tries;
int i;
bitbang_calibrate_delay();
pgm->powerup(pgm);
usleep(20000);
/* TPIDATA is a single line, so MISO & MOSI should be connected */
if (p->flags & AVRPART_HAS_TPI) {
/* make sure cmd_tpi() is defined */
if (pgm->cmd_tpi == NULL) {
fprintf(stderr, "%s: Error: %s programmer does not support TPI\n",
progname, pgm->type);
return -1;
}
/* bring RESET high first */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_RESET], 1);
usleep(1000);
if (verbose >= 2)
fprintf(stderr, "doing MOSI-MISO link check\n");
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], 0);
if (pgm->getpin(pgm, pgm->pinno[PIN_AVR_MISO]) != 0) {
fprintf(stderr, "MOSI->MISO 0 failed\n");
return -1;
}
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], 1);
if (pgm->getpin(pgm, pgm->pinno[PIN_AVR_MISO]) != 1) {
fprintf(stderr, "MOSI->MISO 1 failed\n");
return -1;
}
if (verbose >= 2)
fprintf(stderr, "MOSI-MISO link present\n");
}
pgm->setpin(pgm, pgm->pinno[PIN_AVR_SCK], 0);
pgm->setpin(pgm, pgm->pinno[PIN_AVR_RESET], 0);
usleep(20000);
pgm->highpulsepin(pgm, pgm->pinno[PIN_AVR_RESET]);
if (p->flags & AVRPART_HAS_TPI) {
/* keep TPIDATA high for 16 clock cycles */
pgm->setpin(pgm, pgm->pinno[PIN_AVR_MOSI], 1);
for (i = 0; i < 16; i++)
pgm->highpulsepin(pgm, pgm->pinno[PIN_AVR_SCK]);
/* remove extra guard timing bits */
bitbang_tpi_tx(pgm, TPI_CMD_SSTCS | TPI_REG_TPIPCR);
bitbang_tpi_tx(pgm, 0x7);
/* read TPI ident reg */
bitbang_tpi_tx(pgm, TPI_CMD_SLDCS | TPI_REG_TPIIR);
rc = bitbang_tpi_rx(pgm);
if (rc != 0x80) {
fprintf(stderr, "TPIIR not correct\n");
return -1;
}
} else {
pgm->highpulsepin(pgm, pgm->pinno[PIN_AVR_RESET]);
}
usleep(20000); /* 20 ms XXX should be a per-chip parameter */

View File

@ -2,6 +2,7 @@
* avrdude - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2000, 2001, 2002, 2003 Brian S. Dean <bsd@bsdhome.com>
* Copyright (C) 2005 Michael Holzt <kju-avr@fqdn.org>
* Copyright 2011 Darell Tan
*
* 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
@ -39,6 +40,8 @@ int bitbang_pgm_led (PROGRAMMER * pgm, int value);
int bitbang_vfy_led (PROGRAMMER * pgm, int value);
int bitbang_cmd (PROGRAMMER * pgm, unsigned char cmd[4],
unsigned char res[4]);
int bitbang_cmd_tpi (PROGRAMMER * pgm, unsigned char cmd[],
int cmd_len, unsigned char res[], int res_len);
int bitbang_spi (PROGRAMMER * pgm, unsigned char cmd[],
unsigned char res[], int count);
int bitbang_chip_erase (PROGRAMMER * pgm, AVRPART * p);

View File

@ -2436,6 +2436,44 @@ Solution: Use the following pin mapping:
@item 6 (GND) @tab GND @tab 2
@end multitable
@item
Problem: I want to program an ATtiny4/5/9/10 device using a serial/parallel
bitbang programmer. How to connect the pins?
Solution: Since TPI has only 1 pin for bi-directional data transfer, both
@var{MISO} and @var{MOSI} pins should be connected to the @var{TPIDATA} pin
on the ATtiny device.
However, a 1K resistor should be placed between the @var{MOSI} and @var{TPIDATA}.
The @var{MISO} pin connects to @var{TPIDATA} directly.
The @var{SCK} pin is connected to @var{TPICLK}.
In addition, the @var{Vcc}, @var{/RESET} and @var{GND} pins should
be connected to their respective ports on the ATtiny device.
@item
Problem: How can I use a FTDI FT232R USB-to-Serial device for bitbang programming?
Solution: When connecting the FT232 directly to the pins of the target Atmel device,
the polarity of the pins defined in the @code{programmer} definition should be
inverted by prefixing a tilde. For example, the @var{dasa} programmer would
look like this when connected via a FT232R device (notice the tildes in
front of pins 7, 4, 3 and 8):
@example
programmer
id = "dasa_ftdi";
desc = "serial port banging, reset=rts sck=dtr mosi=txd miso=cts";
type = serbb;
reset = ~7;
sck = ~4;
mosi = ~3;
miso = ~8;
;
@end example
Note that this uses the FT232 device as a normal serial port, not using the
FTDI drivers in the special bitbang mode.
@item
Problem: My ATtiny4/5/9/10 reads out fine, but any attempt to program
it (through TPI) fails. Instead, the memory retains the old contents.

1
par.c
View File

@ -416,6 +416,7 @@ void par_initpgm(PROGRAMMER * pgm)
pgm->program_enable = bitbang_program_enable;
pgm->chip_erase = bitbang_chip_erase;
pgm->cmd = bitbang_cmd;
pgm->cmd_tpi = bitbang_cmd_tpi;
pgm->spi = bitbang_spi;
pgm->open = par_open;
pgm->close = par_close;

1
pgm.c
View File

@ -118,6 +118,7 @@ PROGRAMMER * pgm_new(void)
* assigned before they are called
*/
pgm->cmd = NULL;
pgm->cmd_tpi = NULL;
pgm->spi = NULL;
pgm->paged_write = NULL;
pgm->paged_load = NULL;

2
pgm.h
View File

@ -78,6 +78,8 @@ typedef struct programmer_t {
int (*chip_erase) (struct programmer_t * pgm, AVRPART * p);
int (*cmd) (struct programmer_t * pgm, unsigned char cmd[4],
unsigned char res[4]);
int (*cmd_tpi) (struct programmer_t * pgm, unsigned char cmd[],
int cmd_len, unsigned char res[], int res_len);
int (*spi) (struct programmer_t * pgm, unsigned char cmd[],
unsigned char res[], int count);
int (*open) (struct programmer_t * pgm, char * port);

View File

@ -300,6 +300,7 @@ void serbb_initpgm(PROGRAMMER *pgm)
pgm->program_enable = bitbang_program_enable;
pgm->chip_erase = bitbang_chip_erase;
pgm->cmd = bitbang_cmd;
pgm->cmd_tpi = bitbang_cmd_tpi;
pgm->open = serbb_open;
pgm->close = serbb_close;
pgm->setpin = serbb_setpin;

View File

@ -361,6 +361,7 @@ void serbb_initpgm(PROGRAMMER *pgm)
pgm->program_enable = bitbang_program_enable;
pgm->chip_erase = bitbang_chip_erase;
pgm->cmd = bitbang_cmd;
pgm->cmd_tpi = bitbang_cmd_tpi;
pgm->open = serbb_open;
pgm->close = serbb_close;
pgm->setpin = serbb_setpin;