qianfan has uploaded this change for review.
Add initial CH347T SPI programmer
Tested with CH347T-EVK flash chip W25Q16.V.
Signed-off-by: qianfan Zhao <qianfanguijin@163.com>
Change-Id: I6da5e308af76693e60308c8165349128e517e09a
---
M Makefile
A ch347t_spi.c
M include/programmer.h
M programmer_table.c
4 files changed, 482 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/29/70529/1
diff --git a/Makefile b/Makefile
index 425b58c..2535153 100644
--- a/Makefile
+++ b/Makefile
@@ -155,6 +155,7 @@
DEPENDS_ON_LIBUSB1 := \
CONFIG_CH341A_SPI \
+ CONFIG_CH347T_SPI \
CONFIG_DEDIPROG \
CONFIG_DEVELOPERBOX_SPI \
CONFIG_DIGILENT_SPI \
@@ -516,6 +517,9 @@
# Winchiphead CH341A
CONFIG_CH341A_SPI ?= yes
+# Winchiphead CH347T
+CONFIG_CH347T_SPI ?= yes
+
# Digilent Development board JTAG
CONFIG_DIGILENT_SPI ?= yes
@@ -767,6 +771,11 @@
PROGRAMMER_OBJS += ch341a_spi.o
endif
+ifeq ($(CONFIG_CH347T_SPI), yes)
+FEATURE_FLAGS += -D'CONFIG_CH347T_SPI=1'
+PROGRAMMER_OBJS += ch347t_spi.o
+endif
+
ifeq ($(CONFIG_DIGILENT_SPI), yes)
FEATURE_FLAGS += -D'CONFIG_DIGILENT_SPI=1'
PROGRAMMER_OBJS += digilent_spi.o
diff --git a/ch347t_spi.c b/ch347t_spi.c
new file mode 100644
index 0000000..87c9bb4
--- /dev/null
+++ b/ch347t_spi.c
@@ -0,0 +1,456 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2022 qianfan Zhao <qianfanguijin@163.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 <libusb.h>
+#include "flash.h"
+#include "platform.h"
+#include "programmer.h"
+
+#define USB_TIMEOUT 1000 /* 1000 ms is plenty and we have no backup strategy anyway. */
+#define WRITE_EP 0x06
+#define READ_EP 0x86
+
+#define CH347_MAX_CS 2
+#define CH347_USB_MPS 512
+#define CH347_MAX_DATA_WRITE 512
+#define CH347_MAX_DATA_READ 4096
+
+enum {
+ CH347_CMD_SPI_INIT = 0xC0,
+ CH347_CMD_SPI_CONTROL,
+ CH347_CMD_SPI_RW, /* read and write */
+ CH347_CMD_SPI_RO, /* read only */
+ CH347_CMD_SPI_WO, /* write only */
+ CH347_CMD_SPI_INFO_RD = 0xCA
+};
+
+enum spi_prescaler {
+ SPI_BAUDRATEPRESCALER_2 = 0x00,
+ SPI_BAUDRATEPRESCALER_4 = 0x08,
+ SPI_BAUDRATEPRESCALER_8 = 0x10,
+ SPI_BAUDRATEPRESCALER_16 = 0x18,
+ SPI_BAUDRATEPRESCALER_32 = 0x20,
+ SPI_BAUDRATEPRESCALER_64 = 0x28,
+ SPI_BAUDRATEPRESCALER_128 = 0x30,
+ SPI_BAUDRATEPRESCALER_256 = 0x38
+};
+
+enum spi_dir {
+ SPI_DIRECTION_2LINES_FULLDUPLEX = 0x0000,
+ SPI_DIRECTION_2LINES_RXONLY = 0x0400,
+ SPI_DIRECTION_1LINE_RX = 0x8000,
+ SPI_DIRECTION_1LINE_TX = 0xc000
+};
+
+enum spi_mode {
+ SPI_MODE_SLAVE = 0x0000,
+ SPI_MODE_MASTER = 0x0104
+};
+
+enum spi_datasize {
+ SPI_DATASIZE_16B = 0x0800,
+ SPI_DATASIZE_8B = 0x0000
+};
+
+enum spi_cpol {
+ SPI_CPOL_LOW = 0,
+ SPI_CPOL_HIGH = 2,
+};
+
+enum spi_cpha {
+ SPI_CPHA_1EDGE = 0,
+ SPI_CPHA_2EDGE = 1
+};
+
+enum spi_firstbit {
+ SPI_FIRSTBIT_LSB = 0x0080,
+ SPI_FIRSTBIT_MSB = 0x0000
+};
+
+enum spi_nss {
+ SPI_NSS_SOFT = 0x0200,
+ SPI_NSS_HARD = 0x0000
+};
+
+#define SPI_MISCCFG_CS0_ACTIVE_HIGH 0x80
+#define SPI_MISCCFG_CS1_ACTIVE_HIGH 0x40
+
+typedef uint8_t __le8;
+typedef uint16_t __le16;
+typedef uint32_t __le32;
+
+struct ch347_spi_param {
+ __le16 spi_direction;
+ __le16 spi_mode;
+ __le16 spi_datasize;
+ __le16 spi_cpol;
+ __le16 spi_cpha;
+ __le16 spi_nss;
+ __le16 spi_prescaler;
+ __le16 spi_firstbit;
+ __le16 spi_crcpoly;
+ __le16 spi_rwdelay_us; /* SpiOutInInterTime(us) */
+ __le8 spi_rx_outb; /* data send on mosi when recving */
+ __le8 spi_misccfg;
+ __le8 reserved[4];
+} __attribute__((packed));
+
+static void ch347_spi_param_set_default(struct ch347_spi_param *p)
+{
+ p->spi_direction = cpu_to_le16(SPI_DIRECTION_2LINES_FULLDUPLEX);
+ p->spi_mode = cpu_to_le16(SPI_MODE_MASTER);
+ p->spi_datasize = cpu_to_le16(SPI_DATASIZE_8B);
+ p->spi_cpol = cpu_to_le16(SPI_CPOL_HIGH); /* SPI MODE3 */
+ p->spi_cpha = cpu_to_le16(SPI_CPHA_2EDGE);
+ p->spi_nss = cpu_to_le16(SPI_NSS_HARD);
+ p->spi_prescaler = cpu_to_le16(SPI_BAUDRATEPRESCALER_16); /* 120 / 16 = 7.5M */
+ p->spi_firstbit = cpu_to_le16(SPI_FIRSTBIT_MSB);
+ p->spi_rx_outb = cpu_to_le8(0xff);
+}
+
+struct ch347_spi_cs_param {
+ #define CH347_SPICS_ACTIVE 0x80
+ #define CH347_SPICS_DEACTIVE 0xC0
+ __le8 active_flag;
+ __le16 active_delay_us;
+ __le16 deactive_delay_us;
+} __attribute__((packed));
+
+struct ch347_spi_csctrl_param {
+ struct ch347_spi_cs_param cs[CH347_MAX_CS];
+} __attribute__((packed));
+
+struct ch347_device {
+ struct libusb_device_handle *handle;
+
+ int channel;
+ struct ch347_spi_param param;
+};
+
+static void msg_pspew_hexdump(const void *data, size_t sz)
+{
+ const size_t bytes_per_line = 64;
+ const uint8_t *p = data;
+
+ for (size_t i = 0; i < sz; i++) {
+ msg_pspew("%02x", p[i]);
+ if (((i + 1) % bytes_per_line) == 0)
+ msg_pspew("\n");
+ }
+
+ if (sz % bytes_per_line)
+ msg_pspew("\n");
+}
+
+/* return negative when failed, 0 on successful */
+static int ch347_write(struct ch347_device *dev, uint8_t cmd, const void *data, size_t sz)
+{
+ uint8_t buf[3 + CH347_MAX_DATA_WRITE];
+ int err = 0;
+
+ while (sz > 0) {
+ size_t n = sz;
+ int transferred = 0;
+
+ if (n > CH347_MAX_DATA_WRITE)
+ n = CH347_MAX_DATA_WRITE;
+
+ buf[0] = cmd;
+ buf[1] = (n >> 0) & 0xff; /* lsb */
+ buf[2] = (n >> 8) & 0xff; /* msb */
+ memcpy(&buf[3], data, n);
+
+ msg_pspew("TX (%03zu):\n", n + 3);
+ msg_pspew_hexdump(buf, n + 3);
+ err = libusb_bulk_transfer(dev->handle, WRITE_EP, buf, n + 3,
+ &transferred, USB_TIMEOUT);
+ if (err < 0) {
+ msg_perr("Sending cmd %02x with %zu bytes data failed(%d %s)\n",
+ cmd, n, err, libusb_error_name(err));
+ break;
+ }
+
+ transferred -= 3;
+ data += transferred;
+ sz -= transferred;
+ }
+
+ return err;
+}
+
+/* return number of bytes read on successful */
+static int ch347_read(struct ch347_device *dev, uint8_t cmd, void *rx, size_t rxsz)
+{
+ uint8_t buf[CH347_USB_MPS];
+ size_t n = 0;
+
+ do {
+ size_t remain = rxsz - n;
+ int err, transferred;
+ size_t length;
+
+ err = libusb_bulk_transfer(dev->handle, READ_EP, buf,
+ sizeof(buf), &transferred, USB_TIMEOUT);
+ if (err < 0) {
+ msg_perr("Read response of cmd %02x failed(%d, %s)\n",
+ cmd, err, libusb_error_name(err));
+ return err;
+ }
+
+ msg_pspew("RX (%03d):\n", transferred);
+ msg_pspew_hexdump(buf, transferred);
+
+ if (buf[0] != cmd) {
+ msg_perr("Read response of cmd %02x failed(bad code %02x)\n",
+ cmd, buf[0]);
+ return -1;
+ } else if (transferred < 3) {
+ msg_perr("Read response of cmd %02x failed(imcompleted %d)\n",
+ cmd, transferred);
+ return -1;
+ }
+
+ transferred -= 3;
+ length = buf[1] | (buf[2] << 8);
+ if (length != (size_t)transferred) {
+ msg_perr("Read length doesn't match(%zu != %d)\n",
+ length, transferred);
+ return -1;
+ }
+
+ if (length > remain)
+ length = remain;
+ memcpy(rx + n, buf + 3, length);
+ n += length;
+ } while (n < rxsz);
+
+ return n;
+}
+
+static int ch347_transfer(struct ch347_device *dev, uint8_t cmd, const void *tx,
+ size_t txsz, void *rx, size_t rxsz)
+{
+ int ret;
+
+ ret = ch347_write(dev, cmd, tx, txsz);
+ if (ret < 0)
+ return ret;
+
+ return ch347_read(dev, cmd, rx, rxsz);
+}
+
+static int ch347_spi_set_param(struct ch347_device *dev, struct ch347_spi_param *p)
+{
+ __le8 ack = 0xff;
+ int ret;
+
+ ret = ch347_transfer(dev, CH347_CMD_SPI_INIT, p, sizeof(*p),
+ &ack, sizeof(ack));
+ if (ret < 0)
+ return ret;
+
+ if (le_to_cpu8(ack) != 0x00) {
+ msg_perr("set param failed(%02x)\n", le_to_cpu8(ack));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ch347_spi_set_cs(struct ch347_device *dev, int isactive)
+{
+ struct ch347_spi_csctrl_param csctrl;
+ struct ch347_spi_cs_param *cs = &csctrl.cs[dev->channel];
+
+ memset(&csctrl, 0, sizeof(csctrl));
+ cs->active_flag = isactive ? CH347_SPICS_ACTIVE : CH347_SPICS_DEACTIVE;
+
+ return ch347_write(dev, CH347_CMD_SPI_CONTROL, &csctrl, sizeof(csctrl));
+}
+
+static int ch347_spi_write_only(struct ch347_device *dev, const void *tx, size_t sz)
+{
+ __le8 ack = 0xff;
+ int ret;
+
+ ret = ch347_transfer(dev, CH347_CMD_SPI_WO, tx, sz, &ack, sizeof(ack));
+ if (ret < 0)
+ return ret;
+
+ if (le_to_cpu8(ack) != 0x00) {
+ msg_perr("write %zu bytes data failed(%02x)\n",
+ sz, le_to_cpu8(ack));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ch347_spi_read_only(struct ch347_device *dev, void *rx, size_t sz)
+{
+ __le32 rxsz = cpu_to_le32(sz);
+ int ret;
+
+ ret = ch347_transfer(dev, CH347_CMD_SPI_RO, &rxsz, sizeof(rxsz), rx, sz);
+ if (ret < 0)
+ return ret;
+
+ if (ret != (int)sz) {
+ msg_perr("read incompleted(%zu != %d)\n",
+ sz, ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct ch347_device ch347_device = {
+ .handle = NULL,
+};
+
+static const struct dev_entry devs_ch347t_spi[] = {
+ {0x1A86, 0x55DB, OK, "Winchiphead (WCH)", "CH347T"},
+ {0},
+};
+
+static int ch347t_spi_spi_send_command(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
+{
+ struct ch347_device *dev = &ch347_device;
+ int ret;
+
+ if (!dev->handle)
+ return -1;
+
+ ret = ch347_spi_set_cs(dev, 1);
+ if (ret < 0)
+ return ret;
+
+ if (writecnt) {
+ ret = ch347_spi_write_only(dev, writearr, writecnt);
+ if (ret < 0)
+ goto release;
+ }
+
+ if (readcnt) {
+ ret = ch347_spi_read_only(dev, readarr, readcnt);
+ if (ret < 0)
+ goto release;
+ }
+
+release:
+ ch347_spi_set_cs(dev, 0);
+ return ret;
+}
+
+static int ch347t_spi_shutdown(void *data)
+{
+ struct ch347_device *dev = &ch347_device;
+ struct libusb_device_handle *handle = dev->handle;
+
+ if (!handle)
+ return -1;
+
+ libusb_release_interface(handle, 0);
+ libusb_attach_kernel_driver(handle, 0);
+ libusb_close(handle);
+ libusb_exit(NULL);
+ dev->handle = NULL;
+ return 0;
+}
+
+static const struct spi_master spi_master_ch347t_spi = {
+ .features = SPI_MASTER_4BA,
+ .max_data_read = CH347_MAX_DATA_READ,
+ .max_data_write = CH347_MAX_DATA_WRITE,
+ .command = ch347t_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,
+ .shutdown = ch347t_spi_shutdown,
+ .probe_opcode = default_spi_probe_opcode,
+};
+
+static int ch347t_spi_init(const struct programmer_cfg *cfg)
+{
+ struct ch347_device *dev = &ch347_device;
+ struct libusb_device_handle *handle;
+ int ret;
+
+ if (dev->handle != NULL) {
+ msg_cerr("%s: handle already set! Please report a bug at flashrom@flashrom.org\n", __func__);
+ return -1;
+ }
+
+ ret = libusb_init(NULL);
+ if (ret < 0) {
+ msg_perr("Couldn't initialize libusb!\n");
+ 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_ch347t_spi[0].vendor_id;
+ uint16_t pid = devs_ch347t_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;
+ }
+
+ ret = libusb_detach_kernel_driver(handle, 0);
+ 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(handle, 0);
+ if (ret != 0) {
+ msg_perr("Failed to claim interface 0: '%s'\n", libusb_error_name(ret));
+ goto close_handle;
+ }
+
+ dev->handle = handle;
+ ch347_spi_param_set_default(&dev->param);
+
+ ret = ch347_spi_set_param(dev, &dev->param);
+ if (ret < 0) {
+ msg_perr("Failed to init spi.\n");
+ goto release_interface;
+ }
+
+ return register_spi_master(&spi_master_ch347t_spi, NULL);
+
+release_interface:
+ libusb_release_interface(handle, 0);
+close_handle:
+ libusb_attach_kernel_driver(handle, 0);
+ libusb_close(handle);
+ handle = NULL;
+ return -1;
+}
+
+const struct programmer_entry programmer_ch347t_spi = {
+ .name = "ch347t_spi",
+ .type = USB,
+ .devs.dev = devs_ch347t_spi,
+ .init = ch347t_spi_init,
+};
diff --git a/include/programmer.h b/include/programmer.h
index 7bcef02..b962342 100644
--- a/include/programmer.h
+++ b/include/programmer.h
@@ -62,6 +62,7 @@
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_ch347t_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/programmer_table.c b/programmer_table.c
index d58a155..c8911b6 100644
--- a/programmer_table.c
+++ b/programmer_table.c
@@ -152,6 +152,10 @@
&programmer_ch341a_spi,
#endif
+#if CONFIG_CH347T_SPI == 1
+ &programmer_ch347t_spi,
+#endif
+
#if CONFIG_DIGILENT_SPI == 1
&programmer_digilent_spi,
#endif
To view, visit change 70529. To unsubscribe, or for help writing mail filters, visit settings.