diff --git a/MAINTAINERS b/MAINTAINERS index d1c462977..03f12b824 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -122,6 +122,11 @@ M: Arthur Heymans S: Maintained F: sb600spi.c +CH347 +M: Nicholas Chin +S: Maintained +F: ch347_spi.c + I2C PROGRAMMERS M: Peter Marheine S: Supported diff --git a/Makefile b/Makefile index a7200dd6e..5283feae0 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 000000000..c3326b0c0 --- /dev/null +++ b/ch347_spi.c @@ -0,0 +1,345 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2022 Nicholas Chin + * + * 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 +#include +#include +#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 dd4864dbc..d740db800 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 19363c29e..f72f1d7b4 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 83fc12023..d20f24cd4 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 10427c8fc..307b55197 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 09351679f..79acd7794 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 0e9d7047a..9b490dc3d 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" + 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 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" +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