Signed-off-by: Urja Rannikko <urjaman(a)gmail.com>
Signed-off-by: Stefan Tauner <stefan.tauner(a)alumni.tuwien.ac.at>
---
Makefile | 23 ++-
ch341a_spi.c | 531 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
flashrom.8.tmpl | 12 +-
flashrom.c | 12 ++
programmer.h | 13 ++
5 files changed, 588 insertions(+), 3 deletions(-)
create mode 100644 ch341a_spi.c
diff --git a/Makefile b/Makefile
index 6862200..676bb66 100644
--- a/Makefile
+++ b/Makefile
@@ -157,7 +157,7 @@ UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes
else
override CONFIG_PONY_SPI = no
endif
-# Dediprog, USB-Blaster, PICkit2 and FT2232 are not supported under DOS (missing USB support).
+# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported under DOS (missing USB support).
ifeq ($(CONFIG_DEDIPROG), yes)
UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes
else
@@ -178,6 +178,11 @@ UNSUPPORTED_FEATURES += CONFIG_PICKIT2_SPI=yes
else
override CONFIG_PICKIT2_SPI = no
endif
+ifeq ($(CONFIG_CH341A_SPI), yes)
+UNSUPPORTED_FEATURES += CONFIG_CH341A_SPI=yes
+else
+override CONFIG_CH341A_SPI = no
+endif
endif
# FIXME: Should we check for Cygwin/MSVC as well?
@@ -301,7 +306,7 @@ UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes
else
override CONFIG_PONY_SPI = no
endif
-# Dediprog, USB-Blaster, PICkit2 and FT2232 are not supported with libpayload (missing libusb support)
+# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported with libpayload (missing libusb support)
ifeq ($(CONFIG_DEDIPROG), yes)
UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes
else
@@ -322,6 +327,11 @@ UNSUPPORTED_FEATURES += CONFIG_PICKIT2_SPI=yes
else
override CONFIG_PICKIT2_SPI = no
endif
+ifeq ($(CONFIG_CH341A_SPI), yes)
+UNSUPPORTED_FEATURES += CONFIG_CH341A_SPI=yes
+else
+override CONFIG_CH341A_SPI = no
+endif
endif
ifneq ($(TARGET_OS), Linux)
@@ -500,6 +510,9 @@ CONFIG_LINUX_SPI ?= yes
# Always enable ITE IT8212F PATA controllers for now.
CONFIG_IT8212 ?= yes
+# Winchiphead CH341A
+CONFIG_CH341A_SPI ?= yes
+
# Disable wiki printing by default. It is only useful if you have wiki access.
CONFIG_PRINT_WIKI ?= no
@@ -740,6 +753,12 @@ NEED_LINUX_I2C := yes
PROGRAMMER_OBJS += mstarddc_spi.o
endif
+ifeq ($(CONFIG_CH341A_SPI), yes)
+FEATURE_CFLAGS += -D'CONFIG_CH341A_SPI=1'
+PROGRAMMER_OBJS += ch341a_spi.o
+NEED_LIBUSB1 := yes
+endif
+
ifeq ($(NEED_SERIAL), yes)
LIB_OBJS += serial.o
endif
diff --git a/ch341a_spi.c b/ch341a_spi.c
new file mode 100644
index 0000000..36282ed
--- /dev/null
+++ b/ch341a_spi.c
@@ -0,0 +1,531 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2011 asbokid <ballymunboy(a)gmail.com>
+ * Copyright (C) 2014 Pluto Yang <yangyj.ee(a)gmail.com>
+ * Copyright (C) 2015-2016 Stefan Tauner
+ * Copyright (C) 2015 Urja Rannikko <urjaman(a)gmail.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string.h>
+#include <libusb.h>
+#include "flash.h"
+#include "programmer.h"
+
+/* LIBUSB_CALL ensures the right calling conventions on libusb callbacks.
+ * However, the macro is not defined everywhere. m(
+ */
+#ifndef LIBUSB_CALL
+#define LIBUSB_CALL
+#endif
+
+#define USB_TIMEOUT 1000 /* 1000 ms is plenty and we have no backup strategy anyway. */
+#define WRITE_EP 0x02
+#define READ_EP 0x82
+
+#define CH341_PACKET_LENGTH 0x20
+#define CH341_MAX_PACKETS 256
+#define CH341_MAX_PACKET_LEN (CH341_PACKET_LENGTH * CH341_MAX_PACKETS)
+
+#define CH341A_CMD_SET_OUTPUT 0xA1
+#define CH341A_CMD_IO_ADDR 0xA2
+#define CH341A_CMD_PRINT_OUT 0xA3
+#define CH341A_CMD_SPI_STREAM 0xA8
+#define CH341A_CMD_SIO_STREAM 0xA9
+#define CH341A_CMD_I2C_STREAM 0xAA
+#define CH341A_CMD_UIO_STREAM 0xAB
+
+#define CH341A_CMD_I2C_STM_START 0x74
+#define CH341A_CMD_I2C_STM_STOP 0x75
+#define CH341A_CMD_I2C_STM_OUT 0x80
+#define CH341A_CMD_I2C_STM_IN 0xC0
+#define CH341A_CMD_I2C_STM_MAX ( min( 0x3F, CH341_PACKET_LENGTH ) )
+#define CH341A_CMD_I2C_STM_SET 0x60 // bit 2: SPI with two data pairs D5,D4=out, D7,D6=in
+#define CH341A_CMD_I2C_STM_US 0x40
+#define CH341A_CMD_I2C_STM_MS 0x50
+#define CH341A_CMD_I2C_STM_DLY 0x0F
+#define CH341A_CMD_I2C_STM_END 0x00
+
+#define CH341A_CMD_UIO_STM_IN 0x00
+#define CH341A_CMD_UIO_STM_DIR 0x40
+#define CH341A_CMD_UIO_STM_OUT 0x80
+#define CH341A_CMD_UIO_STM_US 0xC0
+#define CH341A_CMD_UIO_STM_END 0x20
+
+#define CH341A_STM_I2C_20K 0x00
+#define CH341A_STM_I2C_100K 0x01
+#define CH341A_STM_I2C_400K 0x02
+#define CH341A_STM_I2C_750K 0x03
+#define CH341A_STM_SPI_DBL 0x04
+
+
+/* Number of parallel IN transfers. 32 seems to produce the most stable throughput on Windows. */
+#define USB_IN_TRANSFERS 32
+
+/* We need to use many queued IN transfers for any resemblance of performance (especially on Windows)
+ * because USB spec says that transfers end on non-full packets and the device sends the 31 reply
+ * data bytes to each 32-byte packet with command + 31 bytes of data... */
+static struct libusb_transfer *transfer_out = NULL;
+static struct libusb_transfer *transfer_ins[USB_IN_TRANSFERS] = {0};
+
+/* Accumulate delays to be plucked between CS deassertion and CS assertions. */
+static unsigned int stored_delay_us = 0;
+
+static struct libusb_device_handle *handle = NULL;
+
+const struct dev_entry devs_ch341a_spi[] = {
+ {0x1A86, 0x5512, OK, "Winchiphead (WCH)", "CH341A"},
+
+ {0},
+};
+
+enum trans_state {TRANS_ACTIVE = -2, TRANS_ERR = -1, TRANS_IDLE = 0};
+
+static void print_hex(const void *buf, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len; i++) {
+ msg_pspew(" %02x", ((uint8_t *)buf)[i]);
+ if (i % CH341_PACKET_LENGTH == CH341_PACKET_LENGTH - 1)
+ msg_pspew("\n");
+ }
+}
+
+static void cb_common(const char *func, struct libusb_transfer *transfer)
+{
+ int *transfer_cnt = (int*)transfer->user_data;
+
+ if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
+ /* Silently ACK and exit. */
+ *transfer_cnt = TRANS_IDLE;
+ return;
+ }
+
+ if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
+ msg_perr("\n%s: error: %s\n", func, libusb_error_name(transfer->status));
+ *transfer_cnt = TRANS_ERR;
+ } else {
+ *transfer_cnt = transfer->actual_length;
+ }
+}
+
+/* callback for bulk out async transfer */
+static void LIBUSB_CALL cb_out(struct libusb_transfer *transfer)
+{
+ cb_common(__func__, transfer);
+}
+
+/* callback for bulk in async transfer */
+static void LIBUSB_CALL cb_in(struct libusb_transfer *transfer)
+{
+ cb_common(__func__, transfer);
+}
+
+static int32_t usb_transfer(const char *func, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
+{
+ if (handle == NULL)
+ return -1;
+
+ int state_out = TRANS_IDLE;
+ transfer_out->buffer = (uint8_t*)writearr;
+ transfer_out->length = writecnt;
+ transfer_out->user_data = &state_out;
+
+ /* Schedule write first */
+ if (writecnt > 0) {
+ state_out = TRANS_ACTIVE;
+ int ret = libusb_submit_transfer(transfer_out);
+ if (ret) {
+ msg_perr("%s: failed to submit OUT transfer: %s\n", func, libusb_error_name(ret));
+ state_out = TRANS_ERR;
+ goto err;
+ }
+ }
+
+ /* Handle all asynchronous packets as long as we have stuff to write or read. The write(s) simply need
+ * to complete but we need to scheduling reads as long as we are not done. */
+ unsigned int free_idx = 0; /* The IN transfer we expect to be free next. */
+ unsigned int in_idx = 0; /* The IN transfer we expect to be completed next. */
+ unsigned int in_done = 0;
+ unsigned int in_active = 0;
+ unsigned int out_done = 0;
+ int state_in[USB_IN_TRANSFERS] = {0};
+ do {
+ /* Schedule new reads as long as there are free transfers and unscheduled bytes to read. */
+ while ((in_done + in_active) < readcnt && state_in[free_idx] == TRANS_IDLE) {
+ unsigned int cur_todo = min(CH341_PACKET_LENGTH - 1, readcnt - in_done - in_active);
+ transfer_ins[free_idx]->length = cur_todo;
+ transfer_ins[free_idx]->buffer = readarr;
+ transfer_ins[free_idx]->user_data = &state_in[free_idx];
+ int ret = libusb_submit_transfer(transfer_ins[free_idx]);
+ if (ret) {
+ state_in[free_idx] = TRANS_ERR;
+ msg_perr("%s: failed to submit IN transfer: %s\n",
+ func, libusb_error_name(ret));
+ goto err;
+ }
+ readarr += cur_todo;
+ in_active += cur_todo;
+ state_in[free_idx] = TRANS_ACTIVE;
+ free_idx = (free_idx + 1) % USB_IN_TRANSFERS; /* Increment (and wrap around). */
+ }
+
+ /* Actually get some work done. */
+ libusb_handle_events_timeout(NULL, &(struct timeval){1, 0});
+
+ /* Check for the write */
+ if (out_done < writecnt) {
+ if (state_out == TRANS_ERR) {
+ goto err;
+ } else if (state_out > 0) {
+ out_done += state_out;
+ state_out = TRANS_IDLE;
+ }
+ }
+ /* Check for completed transfers. */
+ while (state_in[in_idx] != TRANS_IDLE && state_in[in_idx] != TRANS_ACTIVE) {
+ if (state_in[in_idx] == TRANS_ERR) {
+ goto err;
+ }
+ /* If a transfer is done, record the number of bytes read and reuse it later. */
+ in_done += state_in[in_idx];
+ in_active -= state_in[in_idx];
+ state_in[in_idx] = TRANS_IDLE;
+ in_idx = (in_idx + 1) % USB_IN_TRANSFERS; /* Increment (and wrap around). */
+ }
+ } while ((out_done < writecnt) || (in_done < readcnt));
+
+ if (out_done > 0) {
+ msg_pspew("Wrote %d bytes:\n", out_done);
+ print_hex(writearr, out_done);
+ msg_pspew("\n\n");
+ }
+ if (in_done > 0) {
+ msg_pspew("Read %d bytes:\n", in_done);
+ print_hex(readarr, in_done);
+ msg_pspew("\n\n");
+ }
+ return 0;
+err:
+ /* Clean up on errors. */
+ msg_perr("%s: Failed to %s %d bytes\n", func, (state_out == TRANS_ERR) ? "write" : "read",
+ (state_out == TRANS_ERR) ? writecnt : readcnt);
+ /* First, we must cancel any ongoing requests and wait for them to be canceled. */
+ if ((writecnt > 0) && (state_out == TRANS_ACTIVE)) {
+ if (libusb_cancel_transfer(transfer_out) != 0)
+ state_out = TRANS_ERR;
+ }
+ if (readcnt > 0) {
+ unsigned int i;
+ for (i = 0; i < USB_IN_TRANSFERS; i++) {
+ if (state_in[i] == TRANS_ACTIVE)
+ if (libusb_cancel_transfer(transfer_ins[i]) != 0)
+ state_in[i] = TRANS_ERR;
+ }
+ }
+
+ /* Wait for cancellations to complete. */
+ while (1) {
+ bool finished = true;
+ if ((writecnt > 0) && (state_out == TRANS_ACTIVE))
+ finished = false;
+ if (readcnt > 0) {
+ unsigned int i;
+ for (i = 0; i < USB_IN_TRANSFERS; i++) {
+ if (state_in[i] == TRANS_ACTIVE)
+ finished = false;
+ }
+ }
+ if (finished)
+ break;
+ libusb_handle_events_timeout(NULL, &(struct timeval){1, 0});
+ }
+ return -1;
+}
+
+/* Set the I2C bus speed (speed(b1b0): 0 = 20kHz; 1 = 100kHz, 2 = 400kHz, 3 = 750kHz).
+ * Set the SPI bus data width (speed(b2): 0 = Single, 1 = Double). */
+static int32_t config_stream(uint32_t speed)
+{
+ if (handle == NULL)
+ return -1;
+
+ uint8_t buf[] = {
+ CH341A_CMD_I2C_STREAM,
+ CH341A_CMD_I2C_STM_SET | (speed & 0x7),
+ CH341A_CMD_I2C_STM_END
+ };
+
+ int32_t ret = usb_transfer(__func__, sizeof(buf), 0, buf, NULL);
+ if (ret < 0) {
+ msg_perr("Could not configure stream interface.\n");
+ }
+ return ret;
+}
+
+/* ch341 requires LSB first, swap the bit order before send and after receive */
+static uint8_t swap_byte(uint8_t x)
+{
+ x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
+ x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
+ x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
+ return x;
+}
+
+/* The assumed map between UIO command bits, pins on CH341A chip and pins on SPI chip:
+ * UIO CH341A SPI CH341A SPI name
+ * 0 D0/15 CS/1 (CS0)
+ * 1 D1/16 unused (CS1)
+ * 2 D2/17 unused (CS2)
+ * 3 D3/18 SCK/6 (DCK)
+ * 4 D4/19 unused (DOUT2)
+ * 5 D5/20 SI/5 (DOUT)
+ * - The UIO stream commands seem to only have 6 bits of output, and D6/D7 are the SPI inputs,
+ * mapped as follows:
+ * D6/21 unused (DIN2)
+ * D7/22 SO/2 (DIN)
+ */
+static int32_t enable_pins(bool enable)
+{
+ uint8_t buf[] = {
+ CH341A_CMD_UIO_STREAM,
+ CH341A_CMD_UIO_STM_OUT | 0x37, // CS high (all of them), SCK=0, DOUT*=1
+ CH341A_CMD_UIO_STM_DIR | (enable ? 0x3F : 0x00), // Interface output enable / disable
+ CH341A_CMD_UIO_STM_END,
+ };
+
+ int32_t ret = usb_transfer(__func__, sizeof(buf), 0, buf, NULL);
+ if (ret < 0) {
+ msg_perr("Could not %sable output pins.\n", enable ? "en" : "dis");
+ }
+ return ret;
+}
+
+/* De-assert and assert CS in one operation. */
+static void pluck_cs(uint8_t *ptr)
+{
+ /* This was measured to give a minumum deassertion time of 2.25 us,
+ * >20x more than needed for most SPI chips (100ns). */
+ int delay_cnt = 2;
+ if (stored_delay_us) {
+ delay_cnt = (stored_delay_us * 4) / 3;
+ stored_delay_us = 0;
+ }
+ *ptr++ = CH341A_CMD_UIO_STREAM;
+ *ptr++ = CH341A_CMD_UIO_STM_OUT | 0x37; /* deasserted */
+ int i;
+ for (i = 0; i < delay_cnt; i++)
+ *ptr++ = CH341A_CMD_UIO_STM_OUT | 0x37; /* "delay" */
+ *ptr++ = CH341A_CMD_UIO_STM_OUT | 0x36; /* asserted */
+ *ptr++ = CH341A_CMD_UIO_STM_END;
+}
+
+void ch341a_spi_delay(unsigned int usecs)
+{
+ /* There is space for 28 bytes instructions of 750 ns each in the CS packet (32 - 4 for the actual CS
+ * instructions), thus max 21 us, but we avoid getting too near to this boundary and use
+ * internal_delay() for durations over 20 us. */
+ if ((usecs + stored_delay_us) > 20) {
+ unsigned int inc = 20 - stored_delay_us;
+ internal_delay(usecs - inc);
+ usecs = inc;
+ }
+ stored_delay_us += usecs;
+}
+
+static int ch341a_spi_spi_send_command(struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
+{
+ if (handle == NULL)
+ return -1;
+
+ /* How many packets ... */
+ const size_t packets = (writecnt + readcnt + CH341_PACKET_LENGTH - 2) / (CH341_PACKET_LENGTH - 1);
+
+ uint8_t wbuf[packets+1][CH341_PACKET_LENGTH];
+ uint8_t rbuf[writecnt + readcnt];
+ memset(wbuf[0], 0, CH341_PACKET_LENGTH); // dont want to write stack random to device...
+
+ uint8_t *ptr = wbuf[0];
+ /* CS usage is optimized by doing both transitions in one packet.
+ * Final transition to deselected state is in the pin disable. */
+ pluck_cs(ptr);
+ unsigned int write_left = writecnt;
+ unsigned int read_left = readcnt;
+ unsigned int p;
+ for (p = 0; p < packets; p++) {
+ unsigned int write_now = min(CH341_PACKET_LENGTH - 1, write_left);
+ unsigned int read_now = min ((CH341_PACKET_LENGTH - 1) - write_now, read_left);
+ ptr = wbuf[p+1];
+ *ptr++ = CH341A_CMD_SPI_STREAM;
+ unsigned int i;
+ for (i = 0; i < write_now; ++i)
+ *ptr++ = swap_byte(*writearr++);
+ if (read_now) {
+ memset(ptr, 0xFF, read_now);
+ read_left -= read_now;
+ }
+ write_left -= write_now;
+ }
+
+ int32_t ret = usb_transfer(__func__, CH341_PACKET_LENGTH + packets + writecnt + readcnt,
+ writecnt + readcnt, wbuf[0], rbuf);
+ if (ret < 0)
+ return -1;
+
+ unsigned int i;
+ for (i = 0; i < readcnt; i++) {
+ /* FIXME: does working on words instead of bytes improve speed? */
+ *readarr++ = swap_byte(rbuf[writecnt + i]);
+ }
+
+ return 0;
+}
+
+static const struct spi_master spi_master_ch341a_spi = {
+ .type = SPI_CONTROLLER_CH341A_SPI,
+ /* TODO: flashrom's current maximum is 256 B. Device was tested on Linux to accept atleast 16 kB. */
+ .max_data_read = 16 * 1024,
+ .max_data_write = 16 * 1024,
+ .command = ch341a_spi_spi_send_command,
+ .multicommand = default_spi_send_multicommand,
+ .read = default_spi_read,
+ .write_256 = default_spi_write_256,
+ .write_aai = default_spi_write_aai,
+};
+
+static int ch341a_spi_shutdown(void *data)
+{
+ if (handle == NULL)
+ return -1;
+
+ enable_pins(false);
+ libusb_free_transfer(transfer_out);
+ transfer_out = NULL;
+ int i;
+ for (i = 0; i < USB_IN_TRANSFERS; i++) {
+ libusb_free_transfer(transfer_ins[i]);
+ transfer_ins[i] = NULL;
+ }
+ libusb_release_interface(handle, 0);
+ libusb_close(handle);
+ libusb_exit(NULL);
+ handle = NULL;
+ return 0;
+}
+
+int ch341a_spi_init(void)
+{
+ if (handle != NULL) {
+ msg_cerr("%s: handle already set! Please report a bug at flashrom(a)flashrom.org\n", __func__);
+ return -1;
+ }
+
+ int32_t ret = libusb_init(NULL);
+ if (ret < 0) {
+ msg_perr("Couldnt initialize libusb!\n");
+ return -1;
+ }
+
+ libusb_set_debug(NULL, 3); // Enable information, warning and error messages (only).
+
+ uint16_t vid = devs_ch341a_spi[0].vendor_id;
+ uint16_t pid = devs_ch341a_spi[0].device_id;
+ handle = libusb_open_device_with_vid_pid(NULL, vid, pid);
+ if (handle == NULL) {
+ msg_perr("Couldn't open device %04x:%04x.\n", vid, pid);
+ return -1;
+ }
+
+/* libusb_detach_kernel_driver() and friends basically only work on Linux. We simply try to detach on Linux
+ * without a lot of passion here. If that works fine else we will fail on claiming the interface anyway. */
+#if IS_LINUX
+ ret = libusb_detach_kernel_driver(handle, 0);
+ if (ret == LIBUSB_ERROR_NOT_SUPPORTED) {
+ msg_pwarn("Detaching kernel drivers is not supported. Further accesses may fail.\n");
+ } else if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND) {
+ msg_pwarn("Failed to detach kernel driver: '%s'. Further accesses will probably fail.\n",
+ libusb_error_name(ret));
+ }
+#endif
+
+ ret = libusb_claim_interface(handle, 0);
+ if (ret != 0) {
+ msg_perr("Failed to claim interface 0: '%s'\n", libusb_error_name(ret));
+ goto close_handle;
+ }
+
+ struct libusb_device *dev;
+ if (!(dev = libusb_get_device(handle))) {
+ msg_perr("Failed to get device from device handle.\n");
+ goto close_handle;
+ }
+
+ struct libusb_device_descriptor desc;
+ ret = libusb_get_device_descriptor(dev, &desc);
+ if (ret < 0) {
+ msg_perr("Failed to get device descriptor: '%s'\n", libusb_error_name(ret));
+ goto release_interface;
+ }
+
+ msg_pdbg("Device revision is %d.%01d.%01d\n",
+ (desc.bcdDevice >> 8) & 0x00FF,
+ (desc.bcdDevice >> 4) & 0x000F,
+ (desc.bcdDevice >> 0) & 0x000F);
+
+ /* Allocate and pre-fill transfer structures. */
+ transfer_out = libusb_alloc_transfer(0);
+ if (!transfer_out) {
+ msg_perr("Failed to alloc libusb OUT transfer\n");
+ goto release_interface;
+ }
+ int i;
+ for (i = 0; i < USB_IN_TRANSFERS; i++) {
+ transfer_ins[i] = libusb_alloc_transfer(0);
+ if (transfer_ins[i] == NULL) {
+ msg_perr("Failed to alloc libusb IN transfer %d\n", i);
+ goto dealloc_transfers;
+ }
+ }
+ /* We use these helpers but dont fill the actual buffer yet. */
+ libusb_fill_bulk_transfer(transfer_out, handle, WRITE_EP, NULL, 0, cb_out, NULL, USB_TIMEOUT);
+ for (i = 0; i < USB_IN_TRANSFERS; i++)
+ libusb_fill_bulk_transfer(transfer_ins[i], handle, READ_EP, NULL, 0, cb_in, NULL, USB_TIMEOUT);
+
+ if ((config_stream(CH341A_STM_I2C_100K) < 0) || (enable_pins(true) < 0))
+ goto dealloc_transfers;
+
+ register_shutdown(ch341a_spi_shutdown, NULL);
+ register_spi_master(&spi_master_ch341a_spi);
+
+ return 0;
+
+dealloc_transfers:
+ for (i = 0; i < USB_IN_TRANSFERS; i++) {
+ if (transfer_ins[i] == NULL)
+ break;
+ libusb_free_transfer(transfer_ins[i]);
+ transfer_ins[i] = NULL;
+ }
+ libusb_free_transfer(transfer_out);
+ transfer_out = NULL;
+release_interface:
+ libusb_release_interface(handle, 0);
+close_handle:
+ libusb_close(handle);
+ handle = NULL;
+ return -1;
+}
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 7c8c70c..0dd8322 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -269,6 +269,8 @@ bitbanging adapter)
.sp
.BR "* pickit2_spi" " (for SPI flash ROMs accessible via Microchip PICkit2)"
.sp
+.BR "* ch341a_spi" " (for SPI flash ROMs attached to WCH CH341A)"
+.sp
Some programmers have optional or mandatory parameters which are described
in detail in the
.B PROGRAMMER-SPECIFIC INFORMATION
@@ -1013,6 +1015,10 @@ an operation), without the
parameter, once the flash read/write operation you intended to perform has completed successfully.
.sp
Please also note that the mstarddc_spi driver only works on Linux.
+.SS
+.BR "ch341a_spi " programmer
+The WCH CH341A programmer does not support any parameters currently. SPI frequency is fixed at 2 MHz, and CS0 is
+used as per the device.
.SH EXAMPLES
To back up and update your BIOS, run
.sp
@@ -1072,6 +1078,9 @@ needs userspace access to a serial port.
.BR dediprog ", " ft2232_spi ", " usbblaster_spi " and " pickit2_spi
need access to the respective USB device via libusb-0.1.
.sp
+.BR ch341a_spi
+needs access to the respective USB device via libusb-1.0.
+.sp
.B dummy
needs no access permissions at all.
.sp
@@ -1079,7 +1088,8 @@ needs no access permissions at all.
.BR gfxnvidia ", " drkaiser ", " satasii ", " satamv ", " atahpt" and " atavia
have to be run as superuser/root, and need additional raw access permission.
.sp
-.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi " and " pickit2_spi
+.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi " and " \
+ch341a_spi
can be run as normal user on most operating systems if appropriate device
permissions are set.
.sp
diff --git a/flashrom.c b/flashrom.c
index 3853e19..0ff088f 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -380,6 +380,18 @@ const struct programmer_entry programmer_table[] = {
},
#endif
+#if CONFIG_CH341A_SPI == 1
+ {
+ .name = "ch341a_spi",
+ .type = USB,
+ .devs.dev = devs_ch341a_spi,
+ .init = ch341a_spi_init,
+ .map_flash_region = fallback_map,
+ .unmap_flash_region = fallback_unmap,
+ .delay = ch341a_spi_delay,
+ },
+#endif
+
{0}, /* This entry corresponds to PROGRAMMER_INVALID. */
};
diff --git a/programmer.h b/programmer.h
index 97f0ffa..b9f8708 100644
--- a/programmer.h
+++ b/programmer.h
@@ -105,6 +105,9 @@ enum programmer {
#if CONFIG_PICKIT2_SPI == 1
PROGRAMMER_PICKIT2_SPI,
#endif
+#if CONFIG_CH341A_SPI == 1
+ PROGRAMMER_CH341A_SPI,
+#endif
PROGRAMMER_INVALID /* This must always be the last entry. */
};
@@ -516,6 +519,13 @@ int linux_spi_init(void);
int dediprog_init(void);
#endif
+/* ch341a_spi.c */
+#if CONFIG_CH341A_SPI == 1
+int ch341a_spi_init(void);
+void ch341a_spi_delay(unsigned int usecs);
+extern const struct dev_entry devs_ch341a_spi[];
+#endif
+
/* flashrom.c */
struct decode_sizes {
uint32_t parallel;
@@ -575,6 +585,9 @@ enum spi_controller {
#if CONFIG_PICKIT2_SPI == 1
SPI_CONTROLLER_PICKIT2,
#endif
+#if CONFIG_CH341A_SPI == 1
+ SPI_CONTROLLER_CH341A_SPI,
+#endif
};
#define MAX_DATA_UNSPECIFIED 0
--
Kind regards, Stefan Tauner