Files
avrdude/src/ch341a.c
2026-06-18 19:37:49 +01:00

491 lines
13 KiB
C

/*
* avrdude - A Downloader/Uploader for AVR device programmers
*
* avrdude support for CH341A/B
* Copyright (C) 2016 Alexey Sadkov, paged access by smr 2023
*
* 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, see <http://www.gnu.org/licenses/>.
*/
// Interface to the CH341A programmer
#include <ac_cfg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "avrdude.h"
#include "libavrdude.h"
#include "ch341a.h"
#include "usbdevs.h"
#if defined(HAVE_LIBUSB_1_0)
#define USE_LIBUSB_1_0
#if defined(HAVE_LIBUSB_1_0_LIBUSB_H)
#include <libusb-1.0/libusb.h>
#else
#include <libusb.h>
#endif
#include <sys/time.h>
#ifdef USE_LIBUSB_1_0
static int libusb_to_errno(int result) {
switch(result) {
case LIBUSB_SUCCESS:
return 0;
case LIBUSB_ERROR_IO:
return EIO;
case LIBUSB_ERROR_INVALID_PARAM:
return EINVAL;
case LIBUSB_ERROR_ACCESS:
return EACCES;
case LIBUSB_ERROR_NO_DEVICE:
return ENXIO;
case LIBUSB_ERROR_NOT_FOUND:
return ENOENT;
case LIBUSB_ERROR_BUSY:
return EBUSY;
#ifdef ETIMEDOUT
case LIBUSB_ERROR_TIMEOUT:
return ETIMEDOUT;
#endif
#ifdef EOVERFLOW
case LIBUSB_ERROR_OVERFLOW:
return EOVERFLOW;
#endif
case LIBUSB_ERROR_PIPE:
return EPIPE;
case LIBUSB_ERROR_INTERRUPTED:
return EINTR;
case LIBUSB_ERROR_NO_MEM:
return ENOMEM;
case LIBUSB_ERROR_NOT_SUPPORTED:
return ENOSYS;
default:
return ERANGE;
}
}
#endif
// Private data for this programmer
struct pdata {
libusb_device_handle *usbhandle;
int sckfreq_hz;
libusb_context *ctx;
int USB_init; // Used in ch341a_open()
};
#define my (*(struct pdata *) (pgm->cookie))
// ----------------------------------------------------------------------
static void ch341a_setup(PROGRAMMER *pgm);
static void ch341a_teardown(PROGRAMMER *pgm);
static int ch341a_open(PROGRAMMER *pgm, const char *port);
static void ch341a_close(PROGRAMMER *pgm);
static int ch341a_initialize(const PROGRAMMER *pgm, const AVRPART *p);
static int ch341a_spi_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res);
static int ch341a_spi(const PROGRAMMER *pgm, const unsigned char *in, unsigned char *out, int size);
static int ch341a_spi_program_enable(const PROGRAMMER *pgm, const AVRPART *p);
static int ch341a_spi_chip_erase(const PROGRAMMER *pgm, const AVRPART *p);
static void ch341a_disable(const PROGRAMMER *pgm);
static void ch341a_enable(PROGRAMMER *pgm, const AVRPART *p);
static void ch341a_display(const PROGRAMMER *pgm, const char *p);
// ch341 requires LSB first: invert the bit order before sending and after receiving
static unsigned char swap_byte(unsigned char byte) {
byte = ((byte >> 1) & 0x55) | ((byte << 1) & 0xaa);
byte = ((byte >> 2) & 0x33) | ((byte << 2) & 0xcc);
byte = ((byte >> 4) & 0x0f) | ((byte << 4) & 0xf0);
return byte;
}
static int CH341USBTransferPart(const PROGRAMMER *pgm, enum libusb_endpoint_direction dir,
unsigned char *buff, unsigned int size) {
int ret, bytestransferred;
if(!my.usbhandle)
return 0;
if((ret = libusb_bulk_transfer(my.usbhandle, CH341A_USB_BULK_ENDPOINT | dir,
buff, size, &bytestransferred, CH341A_USB_TIMEOUT))) {
pmsg_error("libusb_bulk_transfer for IN_EP failed, return value %d (%s)\n", ret, libusb_error_name(ret));
return -1;
}
return bytestransferred;
}
static bool CH341USBTransfer(const PROGRAMMER *pgm, enum libusb_endpoint_direction dir,
unsigned char *buff, unsigned int size) {
int pos = 0, bytestransferred;
while(size) {
bytestransferred = CH341USBTransferPart(pgm, dir, buff + pos, size);
if(bytestransferred <= 0)
return false;
pos += bytestransferred;
size -= bytestransferred;
}
return true;
}
/*
* Below the assumed map between UIO command bits, pins on CH341A chip and pins
* on SPI chip. The UIO stream commands only have 6 bits of output, D6/D7 are
* SPI inputs.
*
* UIO CH341A pin/name AVR target
* -------------------------------------------
* D0 15/CS0 RESET
* D1 16/CS1 (unused)
* D2 17/CS2 (unused)
* D3 18/DCK SCK
* D4 19/DOUT2 (unused)
* D5 20/DOUT SDI
* D6 21/DIN2 (unused)
* D7 22/DIN SDO
*/
bool CH341ChipSelect(const PROGRAMMER *pgm, unsigned int cs, bool enable) {
unsigned char cmd[4], res[4];
memset(cmd, 0, sizeof(cmd));
memset(res, 0, sizeof(res));
pmsg_trace("ch341a_ChipSelect()\n");
if(cs > 2) {
pmsg_error("invalid CS pin %d, 0~2 are available\n", cs);
return false;
}
cmd[0] = CH341A_CMD_UIO_STREAM;
if(enable)
cmd[1] = CH341A_CMD_UIO_STM_OUT | (0x37 & ~(1 << cs));
else
cmd[1] = CH341A_CMD_UIO_STM_OUT | 0x37;
cmd[2] = CH341A_CMD_UIO_STM_DIR | 0x3F;
cmd[3] = CH341A_CMD_UIO_STM_END;
return CH341USBTransferPart(pgm, LIBUSB_ENDPOINT_OUT, cmd, 4) > 0;
}
static int ch341a_open(PROGRAMMER *pgm, const char *port) {
int pid, vid, j, r, numids;
int errorCode = USB_ERROR_NOTFOUND;
libusb_device_handle *handle = NULL;
char serno[64] = {0};
unsigned short new_vid = 0, new_pid = 0;
pmsg_debug("%s(\"%s\")\n", __func__, port);
// Set new_vid, new_pid and/or serno from -P usb[:<vid>:<pid>]
if((numids = str_set_vid_pid_serno(port, &new_vid, &new_pid, serno, sizeof serno)) < 0) {
pmsg_error("invalid -P %s; drop -P option or use -P usb[:<vid>:<pid>]\n", port);
return LIBAVRDUDE_EXIT_FAIL;
}
if(*serno)
pmsg_warning("ignoring serial number %s given in -P %s\n", serno, port);
// Set vid/pid in following priority: command-line, config, default
vid = numids >= 2? new_vid: pgm->usbvid? pgm->usbvid: CH341A_VID;
LNODEID usbpid = lfirst(pgm->usbpid);
pid = numids >= 2? new_pid: usbpid? *(int *) ldata(usbpid): CH341A_PID;
if(numids < 2 && usbpid && lnext(usbpid))
pmsg_warning("using PID 0x%04x, ignoring remaining PIDs in list\n", pid);
if(!my.USB_init) {
my.USB_init = 1;
libusb_init(&my.ctx);
}
libusb_device **dev_list;
int dev_list_len = libusb_get_device_list(my.ctx, &dev_list);
for(j = 0; j < dev_list_len; ++j) {
libusb_device *dev = dev_list[j];
struct libusb_device_descriptor descriptor;
libusb_get_device_descriptor(dev, &descriptor);
if(descriptor.idVendor == vid && descriptor.idProduct == pid) {
r = libusb_open(dev, &handle);
if(!handle) {
errorCode = USB_ERROR_ACCESS;
pmsg_warning("cannot open USB device: %s\n", strerror(libusb_to_errno(r)));
continue;
}
}
}
libusb_free_device_list(dev_list, 1);
if(handle != NULL) {
errorCode = 0;
my.usbhandle = handle;
}
if(errorCode != 0) {
pmsg_error("could not find USB device with vid=0x%x pid=0x%x\n", vid, pid);
return -1;
}
if((r = libusb_claim_interface(my.usbhandle, 0))) {
pmsg_error("libusb_claim_interface failed, return value %d (%s)\n", r, libusb_error_name(r));
libusb_close(my.usbhandle);
libusb_exit(my.ctx);
return -1;
}
if(pgm->bitclock)
pmsg_warning("-c %s does not support adjustable bitclock speed; ignoring -B\n", pgmid);
return 0;
}
static void ch341a_close(PROGRAMMER *pgm) {
pmsg_trace("ch341a_close()\n");
int cs = intlog2(pgm->pin[PIN_AVR_RESET].mask[0]);
if(cs < 0)
cs = 0;
CH341ChipSelect(pgm, cs, false);
if(my.usbhandle != NULL) {
libusb_release_interface(my.usbhandle, 0);
libusb_close(my.usbhandle);
}
libusb_exit(my.ctx);
}
static int ch341a_initialize(const PROGRAMMER *pgm, const AVRPART *p) {
pmsg_trace("ch341a_initialize()\n");
int cs = intlog2(pgm->pin[PIN_AVR_RESET].mask[0]);
if(cs < 0)
cs = 0;
if(!CH341ChipSelect(pgm, cs, false)) {
pmsg_error("CH341ChipSelect(..., false) failed\n");
return -1;
}
usleep(20*1000);
if(!CH341ChipSelect(pgm, cs, true)) {
pmsg_error("CH341ChipSelect(..., true) failed\n");
return -1;
}
return pgm->program_enable(pgm, p);
}
static int ch341a_spi(const PROGRAMMER *pgm, const unsigned char *in, unsigned char *out, int size) {
unsigned char pkt[CH341A_PACKET_LENGTH];
if(!size)
return 0;
if(size > CH341A_PACKET_LENGTH - 1)
size = CH341A_PACKET_LENGTH - 1;
pkt[0] = CH341A_CMD_SPI_STREAM;
for(int i = 0; i < size; i++)
pkt[i + 1] = swap_byte(in[i]);
if(!CH341USBTransfer(pgm, LIBUSB_ENDPOINT_OUT, pkt, size + 1)) {
pmsg_error("failed to transfer data to CH341\n");
return -1;
}
if(!CH341USBTransfer(pgm, LIBUSB_ENDPOINT_IN, pkt, size)) {
pmsg_error("failed to transfer data from CH341\n");
return -1;
}
for(int i = 0; i < size; i++)
out[i] = swap_byte(pkt[i]);
return size;
}
static int ch341a_spi_cmd(const PROGRAMMER *pgm, const unsigned char *cmd, unsigned char *res) {
return pgm->spi(pgm, cmd, res, 4);
}
static int ch341a_spi_chip_erase(const PROGRAMMER *pgm, const AVRPART *p) {
unsigned char cmd[4];
unsigned char res[4];
if(p->op[AVR_OP_CHIP_ERASE] == NULL) {
pmsg_error("chip erase instruction not defined for part %s\n", p->desc);
return -1;
}
memset(cmd, 0, sizeof(cmd));
avr_set_bits(p->op[AVR_OP_CHIP_ERASE], cmd);
pgm->cmd(pgm, cmd, res);
usleep(p->chip_erase_delay);
pgm->initialize(pgm, p);
return 0;
}
// Fall back on bytewise write (followed by write page if flash)
static int ch341a_spi_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
int isflash = mem_is_in_flash(m);
if(n_bytes) {
if(!isflash && !mem_is_eeprom(m))
return -2;
// Always called with addr at page boundary and n_bytes == m->page_size
for(unsigned int end = addr + n_bytes; addr < end; addr++)
if(pgm->write_byte(pgm, p, m, addr, m->buf[addr]) < 0)
return -1;
}
if(isflash && avr_write_page(pgm, p, m, addr - n_bytes) < 0)
return -1;
return n_bytes;
}
// Fall back on bytewise read
static int ch341a_spi_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m,
unsigned int page_size, unsigned int addr, unsigned int n_bytes) {
int isflash = mem_is_in_flash(m);
if(n_bytes) {
if(!isflash && !mem_is_eeprom(m))
return -2;
// Always called with addr at page boundary and n_bytes == m->page_size
if(isflash && m->op[AVR_OP_LOAD_EXT_ADDR]) {
unsigned char cmd[4], res[4];
memset(cmd, 0, sizeof cmd);
avr_set_bits(m->op[AVR_OP_LOAD_EXT_ADDR], cmd);
avr_set_addr(m->op[AVR_OP_LOAD_EXT_ADDR], cmd, addr/2);
if(pgm->cmd(pgm, cmd, res) < 0)
return -1;
}
for(unsigned int end = addr + n_bytes; addr < end; addr++)
if(pgm->read_byte(pgm, p, m, addr, m->buf + addr) < 0)
return -1;
}
return n_bytes;
}
static int ch341a_spi_program_enable(const PROGRAMMER *pgm, const AVRPART *p) {
unsigned char res[4];
unsigned char cmd[4];
memset(cmd, 0, sizeof(cmd));
memset(res, 0, sizeof(res));
cmd[0] = 0;
pmsg_trace("ch341a_program_enable() %p\n", p->op[AVR_OP_PGM_ENABLE]);
if(p->op[AVR_OP_PGM_ENABLE] == NULL) {
pmsg_error("program enable instruction not defined for part %s\n", p->desc);
return -1;
}
avr_set_bits(p->op[AVR_OP_PGM_ENABLE], cmd);
pgm->cmd(pgm, cmd, res);
pmsg_debug("%s resp = %02x %02x %02x %02x\n", __func__, res[0], res[1], res[2], res[3]);
// Check for sync character
if(res[2] != cmd[1])
return -2;
return 0;
}
// Interface management
static void ch341a_setup(PROGRAMMER *pgm) {
pgm->cookie = mmt_malloc(sizeof(struct pdata));
}
static void ch341a_teardown(PROGRAMMER *pgm) {
mmt_free(pgm->cookie);
pgm->cookie = NULL;
}
// Dummy functions
static void ch341a_disable(const PROGRAMMER *pgm) {
return;
}
static void ch341a_enable(PROGRAMMER *pgm, const AVRPART *p) {
return;
}
static void ch341a_display(const PROGRAMMER *pgm, const char *p) {
return;
}
void ch341a_initpgm(PROGRAMMER *pgm) {
strcpy(pgm->type, "ch341a");
// Mandatory functions
pgm->initialize = ch341a_initialize;
pgm->display = ch341a_display;
pgm->enable = ch341a_enable;
pgm->disable = ch341a_disable;
pgm->program_enable = ch341a_spi_program_enable;
pgm->chip_erase = ch341a_spi_chip_erase;
pgm->cmd = ch341a_spi_cmd;
pgm->spi = ch341a_spi;
pgm->open = ch341a_open;
pgm->close = ch341a_close;
pgm->read_byte = avr_read_byte_default;
pgm->write_byte = avr_write_byte_default;
// Optional functions
pgm->paged_write = ch341a_spi_paged_write;
pgm->paged_load = ch341a_spi_paged_load;
pgm->setup = ch341a_setup;
pgm->teardown = ch341a_teardown;
}
// ----------------------------------------------------------------------
#else // !defined(HAVE_LIBUSB_1_0)
static int ch341a_nousb_open(PROGRAMMER *pgm, const char *name) {
pmsg_error("no usb support, please compile again with libusb installed\n");
return -1;
}
void ch341a_initpgm(PROGRAMMER *pgm) {
strcpy(pgm->type, "ch341a");
pgm->open = ch341a_nousb_open;
}
#endif // !defined(HAVE_LIBUSB_1_0)
const char ch341a_desc[] = "Chip CH341A: AVR must have min F_CPU of 6.8 MHz";