aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS5
-rw-r--r--Makefile9
-rw-r--r--ch347_spi.c345
-rw-r--r--flashrom.8.tmpl6
-rw-r--r--include/programmer.h1
-rw-r--r--meson.build6
-rw-r--r--meson_options.txt2
-rw-r--r--programmer_table.c4
-rwxr-xr-xtest_build.sh18
9 files changed, 386 insertions, 10 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index d1c46297..03f12b82 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -122,6 +122,11 @@ M: Arthur Heymans <arthur@aheymans.xyz>
S: Maintained
F: sb600spi.c
+CH347
+M: Nicholas Chin <nic.c3.14@gmail.com>
+S: Maintained
+F: ch347_spi.c
+
I2C PROGRAMMERS
M: Peter Marheine <pmarheine@chromium.org>
S: Supported
diff --git a/Makefile b/Makefile
index a7200dd6..5283feae 100644
--- a/Makefile
+++ b/Makefile
@@ -157,6 +157,7 @@ DEPENDS_ON_LIBPCI := \
DEPENDS_ON_LIBUSB1 := \
CONFIG_CH341A_SPI \
+ CONFIG_CH347_SPI \
CONFIG_DEDIPROG \
CONFIG_DEVELOPERBOX_SPI \
CONFIG_DIGILENT_SPI \
@@ -521,6 +522,9 @@ CONFIG_IT8212 ?= yes
# Winchiphead CH341A
CONFIG_CH341A_SPI ?= yes
+# Winchiphead CH347
+CONFIG_CH347_SPI ?= yes
+
# Digilent Development board JTAG
CONFIG_DIGILENT_SPI ?= yes
@@ -814,6 +818,11 @@ PROGRAMMER_OBJS += ch341a_spi.o
ACTIVE_PROGRAMMERS += ch341a_spi
endif
+ifeq ($(CONFIG_CH347_SPI), yes)
+FEATURE_FLAGS += -D'CONFIG_CH347_SPI=1'
+PROGRAMMER_OBJS += ch347_spi.o
+endif
+
ifeq ($(CONFIG_DIGILENT_SPI), yes)
FEATURE_FLAGS += -D'CONFIG_DIGILENT_SPI=1'
PROGRAMMER_OBJS += digilent_spi.o
diff --git a/ch347_spi.c b/ch347_spi.c
new file mode 100644
index 00000000..c3326b0c
--- /dev/null
+++ b/ch347_spi.c
@@ -0,0 +1,345 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2022 Nicholas Chin <nic.c3.14@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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <libusb.h>
+#include "platform.h"
+#include "programmer.h"
+#include "flash.h"
+
+#define CH347_CMD_SPI_SET_CFG 0xC0
+#define CH347_CMD_SPI_CS_CTRL 0xC1
+#define CH347_CMD_SPI_OUT_IN 0xC2
+#define CH347_CMD_SPI_IN 0xC3
+#define CH347_CMD_SPI_OUT 0xC4
+#define CH347_CMD_SPI_GET_CFG 0xCA
+
+#define CH347_CS_ASSERT 0x00
+#define CH347_CS_DEASSERT 0x40
+#define CH347_CS_CHANGE 0x80
+#define CH347_CS_IGNORE 0x00
+
+#define WRITE_EP 0x06
+#define READ_EP 0x86
+
+#define MODE_1_IFACE 2
+#define MODE_2_IFACE 1
+
+/* The USB descriptor says the max transfer size is 512 bytes, but the
+ * vendor driver only seems to transfer a maximum of 510 bytes at once,
+ * leaving 507 bytes for data as the command + length take up 3 bytes
+ */
+#define CH347_PACKET_SIZE 510
+#define CH347_MAX_DATA_LEN (CH347_PACKET_SIZE - 3)
+
+struct ch347_spi_data {
+ struct libusb_device_handle *handle;
+};
+
+/* TODO: Add support for HID mode */
+static const struct dev_entry devs_ch347_spi[] = {
+ {0x1A86, 0x55DB, OK, "QinHeng Electronics", "USB To UART+SPI+I2C"},
+ {0}
+};
+
+static int ch347_spi_shutdown(void *data)
+{
+ struct ch347_spi_data *ch347_data = data;
+
+ /* TODO: Set this depending on the mode */
+ int spi_interface = MODE_1_IFACE;
+ libusb_release_interface(ch347_data->handle, spi_interface);
+ libusb_attach_kernel_driver(ch347_data->handle, spi_interface);
+ libusb_close(ch347_data->handle);
+ libusb_exit(NULL);
+
+ free(data);
+ return 0;
+}
+
+static int ch347_cs_control(struct ch347_spi_data *ch347_data, uint8_t cs1, uint8_t cs2)
+{
+ uint8_t cmd[13] = {
+ [0] = CH347_CMD_SPI_CS_CTRL,
+ /* payload length, uint16 LSB: 10 */
+ [1] = 10,
+ [3] = cs1,
+ [8] = cs2
+ };
+
+ int32_t ret = libusb_bulk_transfer(ch347_data->handle, WRITE_EP, cmd, sizeof(cmd), NULL, 1000);
+ if (ret < 0) {
+ msg_perr("Could not change CS!\n");
+ return -1;
+ }
+ return 0;
+}
+
+
+static int ch347_write(struct ch347_spi_data *ch347_data, unsigned int writecnt, const uint8_t *writearr)
+{
+ unsigned int data_len;
+ int packet_len;
+ int transferred;
+ int ret;
+ uint8_t resp_buf[4] = {0};
+ uint8_t buffer[CH347_PACKET_SIZE] = {0};
+ unsigned int bytes_written = 0;
+
+ while (bytes_written < writecnt) {
+ data_len = min(CH347_MAX_DATA_LEN, writecnt - bytes_written );
+ packet_len = data_len + 3;
+
+ buffer[0] = CH347_CMD_SPI_OUT;
+ buffer[1] = (data_len) & 0xFF;
+ buffer[2] = ((data_len) & 0xFF00) >> 8;
+ memcpy(buffer + 3, writearr + bytes_written, data_len);
+
+ ret = libusb_bulk_transfer(ch347_data->handle, WRITE_EP, buffer, packet_len, &transferred, 1000);
+ if (ret < 0 || transferred != packet_len) {
+ msg_perr("Could not send write command\n");
+ return -1;
+ }
+
+ ret = libusb_bulk_transfer(ch347_data->handle, READ_EP, resp_buf, sizeof(resp_buf), NULL, 1000);
+ if (ret < 0) {
+ msg_perr("Could not receive write command response\n");
+ return -1;
+ }
+ bytes_written += data_len;
+ }
+ return 0;
+}
+
+static int ch347_read(struct ch347_spi_data *ch347_data, unsigned int readcnt, uint8_t *readarr)
+{
+ uint8_t *read_ptr = readarr;
+ int ret;
+ int transferred;
+ unsigned int bytes_read = 0;
+ uint8_t buffer[CH347_PACKET_SIZE] = {0};
+ uint8_t command_buf[7] = {
+ [0] = CH347_CMD_SPI_IN,
+ [1] = 4,
+ [2] = 0,
+ [3] = readcnt & 0xFF,
+ [4] = (readcnt & 0xFF00) >> 8,
+ [5] = (readcnt & 0xFF0000) >> 16,
+ [6] = (readcnt & 0xFF000000) >> 24
+ };
+
+ ret = libusb_bulk_transfer(ch347_data->handle, WRITE_EP, command_buf, sizeof(command_buf), &transferred, 1000);
+ if (ret < 0 || transferred != sizeof(command_buf)) {
+ msg_perr("Could not send read command\n");
+ return -1;
+ }
+
+ while (bytes_read < readcnt) {
+ ret = libusb_bulk_transfer(ch347_data->handle, READ_EP, buffer, CH347_PACKET_SIZE, &transferred, 1000);
+ if (ret < 0) {
+ msg_perr("Could not read data\n");
+ return -1;
+ }
+ if (transferred > CH347_PACKET_SIZE) {
+ msg_perr("libusb bug: bytes received overflowed buffer\n");
+ return -1;
+ }
+ /* Response: u8 command, u16 data length, then the data that was read */
+ if (transferred < 3) {
+ msg_perr("CH347 returned an invalid response to read command\n");
+ return -1;
+ }
+ int ch347_data_length = read_le16(buffer, 1);
+ if (transferred - 3 < ch347_data_length) {
+ msg_perr("CH347 returned less data than data length header indicates\n");
+ return -1;
+ }
+ bytes_read += ch347_data_length;
+ if (bytes_read > readcnt) {
+ msg_perr("CH347 returned more bytes than requested\n");
+ return -1;
+ }
+ memcpy(read_ptr, buffer + 3, ch347_data_length);
+ read_ptr += ch347_data_length;
+ }
+ return 0;
+}
+
+static int ch347_spi_send_command(const struct flashctx *flash, unsigned int writecnt,
+ unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
+{
+ struct ch347_spi_data *ch347_data = flash->mst->spi.data;
+ int ret = 0;
+
+ ch347_cs_control(ch347_data, CH347_CS_ASSERT | CH347_CS_CHANGE, CH347_CS_IGNORE);
+ if (writecnt) {
+ ret = ch347_write(ch347_data, writecnt, writearr);
+ if (ret < 0) {
+ msg_perr("CH347 write error\n");
+ return -1;
+ }
+ }
+ if (readcnt) {
+ ret = ch347_read(ch347_data, readcnt, readarr);
+ if (ret < 0) {
+ msg_perr("CH347 read error\n");
+ return -1;
+ }
+ }
+ ch347_cs_control(ch347_data, CH347_CS_DEASSERT | CH347_CS_CHANGE, CH347_CS_IGNORE);
+
+ return 0;
+}
+
+static int32_t ch347_spi_config(struct ch347_spi_data *ch347_data, uint8_t divisor)
+{
+ int32_t ret;
+ uint8_t buff[29] = {
+ [0] = CH347_CMD_SPI_SET_CFG,
+ [1] = (sizeof(buff) - 3) & 0xFF,
+ [2] = ((sizeof(buff) - 3) & 0xFF00) >> 8,
+ /* Not sure what these two bytes do, but the vendor
+ * drivers seem to unconditionally set these values
+ */
+ [5] = 4,
+ [6] = 1,
+ /* Clock polarity: bit 1 */
+ [9] = 0,
+ /* Clock phase: bit 0 */
+ [11] = 0,
+ /* Another mystery byte */
+ [14] = 2,
+ /* Clock divisor: bits 5:3 */
+ [15] = (divisor & 0x7) << 3,
+ /* Bit order: bit 7, 0=MSB */
+ [17] = 0,
+ /* Yet another mystery byte */
+ [19] = 7,
+ /* CS polarity: bit 7 CS2, bit 6 CS1. 0 = active low */
+ [24] = 0
+ };
+
+ ret = libusb_bulk_transfer(ch347_data->handle, WRITE_EP, buff, sizeof(buff), NULL, 1000);
+ if (ret < 0) {
+ msg_perr("Could not configure SPI interface\n");
+ }
+
+ /* FIXME: Not sure if the CH347 sends error responses for
+ * invalid config data, if so the code should check
+ */
+ ret = libusb_bulk_transfer(ch347_data->handle, READ_EP, buff, sizeof(buff), NULL, 1000);
+ if (ret < 0) {
+ msg_perr("Could not receive configure SPI command response\n");
+ }
+ return ret;
+}
+
+static const struct spi_master spi_master_ch347_spi = {
+ .features = SPI_MASTER_4BA,
+ .max_data_read = MAX_DATA_READ_UNLIMITED,
+ .max_data_write = MAX_DATA_WRITE_UNLIMITED,
+ .command = ch347_spi_send_command,
+ .multicommand = default_spi_send_multicommand,
+ .read = default_spi_read,
+ .write_256 = default_spi_write_256,
+ .write_aai = default_spi_write_aai,
+ .shutdown = ch347_spi_shutdown,
+ .probe_opcode = default_spi_probe_opcode,
+};
+
+/* Largely copied from ch341a_spi.c */
+static int ch347_spi_init(const struct programmer_cfg *cfg)
+{
+ struct ch347_spi_data *ch347_data = calloc(1, sizeof(*ch347_data));
+ if (!ch347_data) {
+ msg_perr("Could not allocate space for SPI data\n");
+ return 1;
+ }
+
+ int32_t ret = libusb_init(NULL);
+ if (ret < 0) {
+ msg_perr("Could not initialize libusb!\n");
+ free(ch347_data);
+ return 1;
+ }
+
+ /* Enable information, warning, and error messages (only). */
+#if LIBUSB_API_VERSION < 0x01000106
+ libusb_set_debug(NULL, 3);
+#else
+ libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
+#endif
+
+ uint16_t vid = devs_ch347_spi[0].vendor_id;
+ uint16_t pid = devs_ch347_spi[0].device_id;
+ ch347_data->handle = libusb_open_device_with_vid_pid(NULL, vid, pid);
+ if (ch347_data->handle == NULL) {
+ msg_perr("Couldn't open device %04x:%04x.\n", vid, pid);
+ free(ch347_data);
+ return 1;
+ }
+
+ /* TODO: set based on mode */
+ /* Mode 1 uses interface 2 for the SPI interface */
+ int spi_interface = MODE_1_IFACE;
+
+ ret = libusb_detach_kernel_driver(ch347_data->handle, spi_interface);
+ if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND)
+ msg_pwarn("Cannot detach the existing USB driver. Claiming the interface may fail. %s\n",
+ libusb_error_name(ret));
+
+ ret = libusb_claim_interface(ch347_data->handle, spi_interface);
+ if (ret != 0) {
+ msg_perr("Failed to claim interface 2: '%s'\n", libusb_error_name(ret));
+ goto error_exit;
+ }
+
+ struct libusb_device *dev;
+ if (!(dev = libusb_get_device(ch347_data->handle))) {
+ msg_perr("Failed to get device from device handle.\n");
+ goto error_exit;
+ }
+
+ 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 error_exit;
+ }
+
+ msg_pdbg("Device revision is %d.%01d.%01d\n",
+ (desc.bcdDevice >> 8) & 0x00FF,
+ (desc.bcdDevice >> 4) & 0x000F,
+ (desc.bcdDevice >> 0) & 0x000F);
+
+ /* TODO: add programmer cfg for things like CS pin and divisor */
+ if (ch347_spi_config(ch347_data, 2) < 0)
+ goto error_exit;
+
+ return register_spi_master(&spi_master_ch347_spi, ch347_data);
+
+error_exit:
+ ch347_spi_shutdown(ch347_data);
+ return 1;
+}
+
+const struct programmer_entry programmer_ch347_spi = {
+ .name = "ch347_spi",
+ .type = USB,
+ .devs.dev = devs_ch347_spi,
+ .init = ch347_spi_init,
+};
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index dd4864db..d740db80 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -417,6 +417,8 @@ bitbanging adapter)
.sp
.BR "* ch341a_spi" " (for SPI flash ROMs attached to WCH CH341A)"
.sp
+.BR "* ch347_spi" " (for SPI flash ROMs attached to WCH CH347)"
+.sp
.BR "* digilent_spi" " (for SPI flash ROMs attached to iCEblink40 development boards)"
.sp
.BR "* jlink_spi" " (for SPI flash ROMs attached to SEGGER J-Link and compatible devices)"
@@ -1368,6 +1370,10 @@ Please also note that the mstarddc_spi driver only works on Linux.
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.
.SS
+.BR "ch347_spi " programmer
+The WCH CH347 programmer does not currently support any parameters. SPI frequency is fixed at 2 MHz, and CS0 is
+used as per the device.
+.SS
.BR "ni845x_spi " programmer
.IP
An optional
diff --git a/include/programmer.h b/include/programmer.h
index 19363c29..f72f1d7b 100644
--- a/include/programmer.h
+++ b/include/programmer.h
@@ -61,6 +61,7 @@ extern const struct programmer_entry programmer_atapromise;
extern const struct programmer_entry programmer_atavia;
extern const struct programmer_entry programmer_buspirate_spi;
extern const struct programmer_entry programmer_ch341a_spi;
+extern const struct programmer_entry programmer_ch347_spi;
extern const struct programmer_entry programmer_dediprog;
extern const struct programmer_entry programmer_developerbox;
extern const struct programmer_entry programmer_digilent_spi;
diff --git a/meson.build b/meson.build
index 83fc1202..d20f24cd 100644
--- a/meson.build
+++ b/meson.build
@@ -201,6 +201,12 @@ programmer = {
'test_srcs' : files('tests/ch341a_spi.c'),
'flags' : [ '-DCONFIG_CH341A_SPI=1' ],
},
+ 'ch347_spi' : {
+ 'deps' : [ libusb1 ],
+ 'groups' : [ group_usb, group_external ],
+ 'srcs' : files('ch347_spi.c'),
+ 'flags' : [ '-DCONFIG_CH347_SPI=1' ],
+ },
'dediprog' : {
'deps' : [ libusb1 ],
'groups' : [ group_usb, group_external ],
diff --git a/meson_options.txt b/meson_options.txt
index 10427c8f..307b5519 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,7 +10,7 @@ option('programmer', type : 'array', value : ['auto'], choices : [
'auto', 'all',
'group_internal', 'group_external',
'group_ftdi', 'group_i2c', 'group_jlink', 'group_pci', 'group_serial', 'group_usb',
- 'asm106x', 'atahpt', 'atapromise', 'atavia', 'buspirate_spi', 'ch341a_spi', 'dediprog',
+ 'asm106x', 'atahpt', 'atapromise', 'atavia', 'buspirate_spi', 'ch341a_spi', 'ch347_spi','dediprog',
'developerbox_spi', 'digilent_spi', 'dirtyjtag_spi', 'drkaiser', 'dummy', 'ft2232_spi',
'gfxnvidia', 'internal', 'it8212', 'jlink_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi',
'mstarddc_spi', 'nic3com', 'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi',
diff --git a/programmer_table.c b/programmer_table.c
index 09351679..79acd779 100644
--- a/programmer_table.c
+++ b/programmer_table.c
@@ -156,6 +156,10 @@ const struct programmer_entry *const programmer_table[] = {
&programmer_ch341a_spi,
#endif
+#if CONFIG_CH347_SPI == 1
+ &programmer_ch347_spi,
+#endif
+
#if CONFIG_DIGILENT_SPI == 1
&programmer_digilent_spi,
#endif
diff --git a/test_build.sh b/test_build.sh
index 0e9d7047..9b490dc3 100755
--- a/test_build.sh
+++ b/test_build.sh
@@ -10,15 +10,15 @@ make_programmer_opts="INTERNAL INTERNAL_X86 SERPROG RAYER_SPI RAIDEN_DEBUG_SPI P
PICKIT2_SPI STLINKV3_SPI PARADE_LSPCON MEDIATEK_I2C_SPI REALTEK_MST_I2C_SPI DUMMY \
DRKAISER NICREALTEK NICNATSEMI NICINTEL NICINTEL_SPI NICINTEL_EEPROM OGP_SPI \
BUSPIRATE_SPI DEDIPROG DEVELOPERBOX_SPI SATAMV LINUX_MTD LINUX_SPI IT8212 \
- CH341A_SPI DIGILENT_SPI DIRTYJTAG_SPI JLINK_SPI ASM106X"
-
-meson_programmer_opts="all auto group_ftdi group_i2c group_jlink group_pci group_serial group_usb \
- atahpt atapromise atavia buspirate_spi ch341a_spi dediprog developerbox_spi \
- digilent_spi dirtyjtag_spi drkaiser dummy ft2232_spi gfxnvidia internal it8212 \
- jlink_spi linux_mtd linux_spi parade_lspcon mediatek_i2c_spi mstarddc_spi \
- nic3com nicintel nicintel_eeprom nicintel_spi nicnatsemi nicrealtek \
- ogp_spi pickit2_spi pony_spi raiden_debug_spi rayer_spi realtek_mst_i2c_spi \
- satamv satasii serprog stlinkv3_spi usbblaster_spi asm106x"
+ CH341A_SPI CH347_SPI DIGILENT_SPI DIRTYJTAG_SPI JLINK_SPI ASM106X"
+
+meson_programmer_opts="all auto group_ftdi group_i2c group_jlink group_pci group_serial group_usb \
+ atahpt atapromise atavia buspirate_spi ch341a_spi ch347_spi dediprog \
+ developerbox_spi digilent_spi dirtyjtag_spi drkaiser dummy ft2232_spi \
+ gfxnvidia internal it8212 jlink_spi linux_mtd linux_spi parade_lspcon \
+ mediatek_i2c_spi mstarddc_spi nic3com nicintel nicintel_eeprom nicintel_spi \
+ nicnatsemi nicrealtek ogp_spi pickit2_spi pony_spi raiden_debug_spi rayer_spi \
+ realtek_mst_i2c_spi satamv satasii serprog stlinkv3_spi usbblaster_spi asm106x"
if [ "$(basename "${CC}")" = "ccc-analyzer" ] || [ -n "${COVERITY_OUTPUT}" ]; then