[RFC,1/2] programmer: Add Developerbox/CP2104 bit bang driver

Message ID 20180604130244.23912-2-daniel.thompson@linaro.org
State New
Headers show
Series
  • Experimental Developerbox/CP2102 bitbang programmer
Related show

Commit Message

Daniel Thompson June 4, 2018, 1:02 p.m.
The 96Boards Developerbox (a.k.a. Synquacer E-series) provides a CP2102
debug UART with its GPIO pins hooked up to an SPI NOR FLASH. The circuit
is intended to provide emergency de-brick functions without requiring any
additional tools (such as a JTAG or SPI programmer). This was *expected*
to be very slow (and it *is* very slow) but CP2102 is much cheaper than a
full dual channel USB comms chip. It is only really practical as an
emergency debrick tool/

This patch provides an early implementation of a programmer driver. It is
a true RFC... I'd like to tidy it up to upstream and don't want to waste
effort tweaking it if it ends up objectionable to the maintainers.
As shown here it appears to work reasonably well: probe works and it is
able to reliabily read flash (performance is roughly the same as a
2400 baud modem, or ~70 minutes per megabyte).

Questions are:

1. Currently the driver is structured as a simple Developerbox driver
   rather than a CP210x driver and configuration for Developerbox.
   I'm happy to layer it properly (via register_cp210x_bitbang_master()
   or something similar) but equally am happy to leave that as refactoring
   for a future adventurer (I'm not sure encouraging the use of CP210x
   as a programmer would be deemed desirable). Which is better?

2. Take a look at CP2014_FAST... This is clearly a layering violation
   but the performance boost of combining the SCK and MOSI write is
   very useful. Are there any objections to refactoring the bitbang
   interfaces to allow optimized bitbanging?

3. Currently I've copied get_device_by_vid_pid_number() from the dediprog
   code base. I plan to extend it to check the serial number (instead of
   using the enumeration order). Is there any other programmer I should
   look at to refactor this into library code or should I just customise
   this function for CP210x and move on?

Change-Id: I2547a96c1a2259ad0d52cd4b6ef42261b37cccf3
Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org>
---
 Makefile           |  24 +++-
 developerbox_spi.c | 296 +++++++++++++++++++++++++++++++++++++++++++++
 flashrom.c         |  12 ++
 programmer.h       |  12 ++
 4 files changed, 342 insertions(+), 2 deletions(-)
 create mode 100644 developerbox_spi.c

Patch

diff --git a/Makefile b/Makefile
index 943d88d76f77..17cc8d53f139 100644
--- a/Makefile
+++ b/Makefile
@@ -153,12 +153,17 @@  UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes
 else
 override CONFIG_PONY_SPI = no
 endif
-# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported under DOS (missing USB support).
+# Dediprog, Developerbox, USB-Blaster, PICkit2, CH341A and FT2232 are not supported under DOS (missing USB support).
 ifeq ($(CONFIG_DEDIPROG), yes)
 UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes
 else
 override CONFIG_DEDIPROG = no
 endif
+ifeq ($(CONFIG_DEVELOPERBOX), yes)
+UNSUPPORTED_FEATURES += CONFIG_DEVELOPERBOX=yes
+else
+override CONFIG_DEVELOPERBOX = no
+endif
 ifeq ($(CONFIG_FT2232_SPI), yes)
 UNSUPPORTED_FEATURES += CONFIG_FT2232_SPI=yes
 else
@@ -310,12 +315,17 @@  UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes
 else
 override CONFIG_PONY_SPI = no
 endif
-# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported with libpayload (missing libusb support).
+# Dediprog, Developerbox, USB-Blaster, PICkit2, CH341A and FT2232 are not supported with libpayload (missing libusb support).
 ifeq ($(CONFIG_DEDIPROG), yes)
 UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes
 else
 override CONFIG_DEDIPROG = no
 endif
+ifeq ($(CONFIG_DEVELOPERBOX), yes)
+UNSUPPORTED_FEATURES += CONFIG_DEVELOPERBOX=yes
+else
+override CONFIG_DEVELOPERBOX = no
+endif
 ifeq ($(CONFIG_FT2232_SPI), yes)
 UNSUPPORTED_FEATURES += CONFIG_FT2232_SPI=yes
 else
@@ -626,6 +636,9 @@  CONFIG_BUSPIRATE_SPI ?= yes
 # Always enable Dediprog SF100 for now.
 CONFIG_DEDIPROG ?= yes
 
+# Always enable Developerbox emergency recovery for now.
+CONFIG_DEVELOPERBOX ?= yes
+
 # Always enable Marvell SATA controllers for now.
 CONFIG_SATAMV ?= yes
 
@@ -666,6 +679,7 @@  endif
 ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no)
 override CONFIG_CH341A_SPI = no
 override CONFIG_DEDIPROG = no
+override CONFIG_DEVELOPERBOX = no
 endif
 ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no)
 override CONFIG_INTERNAL = no
@@ -902,6 +916,12 @@  PROGRAMMER_OBJS += dediprog.o
 NEED_LIBUSB1 += CONFIG_DEDIPROG
 endif
 
+ifeq ($(CONFIG_DEVELOPERBOX), yes)
+FEATURE_CFLAGS += -D'CONFIG_DEVELOPERBOX=1'
+PROGRAMMER_OBJS += developerbox_spi.o
+NEED_LIBUSB1 += CONFIG_DEVELOPERBOX
+endif
+
 ifeq ($(CONFIG_SATAMV), yes)
 FEATURE_CFLAGS += -D'CONFIG_SATAMV=1'
 PROGRAMMER_OBJS += satamv.o
diff --git a/developerbox_spi.c b/developerbox_spi.c
new file mode 100644
index 000000000000..5742ed093b1a
--- /dev/null
+++ b/developerbox_spi.c
@@ -0,0 +1,296 @@ 
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2018 Linaro Limited
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+
+/* Bit bang driver for the 96Boards Developerbox (a.k.a. Synquacer E-series)
+ * on-board debug UART.  The Developerbox implements its debug UART using a
+ * CP2102N, a USB to UART bridge will also provides four GPIO pins. On
+ * Developerbox these can be hooked up to the onboard SPI NOR FLASH and used
+ * for emergency de-brick without any additional hardware programmer. Bit
+ * banging over USB is extremely slow compared to a proper SPI programmer so
+ * this is only practical as a de-brick tool.
+ *
+ * Schematic is available here:
+ * https://www.96boards.org/documentation/enterprise/developerbox/hardware-docs/
+ */
+
+#include "platform.h"
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <libusb.h>
+#include "flash.h"
+#include "chipdrivers.h"
+#include "programmer.h"
+#include "spi.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
+
+/* To prepare a Developerbox for programming via the debug UART DSW4 must be
+ * changed from the default 00000000 to 10001000 (DSW4-1 and DSW4-5 should be
+ * turned on)
+ */
+
+/* Bit positions for each pin. */
+#define DEVELOPERBOX_SPI_SCK		0
+#define DEVELOPERBOX_SPI_CS		1
+#define DEVELOPERBOX_SPI_MISO		2
+#define DEVELOPERBOX_SPI_MOSI		3
+
+/* Config request types */
+#define REQTYPE_HOST_TO_DEVICE  0x40
+#define REQTYPE_DEVICE_TO_HOST  0xc0
+
+/* Config request codes */
+#define CP210X_VENDOR_SPECIFIC  0xFF
+
+/* CP210X_VENDOR_SPECIFIC */
+#define CP210X_WRITE_LATCH      0x37E1
+#define CP210X_READ_LATCH       0x00C2
+
+const struct dev_entry devs_developerbox[] = {
+	{0x10C4, 0xEA60, OK, "Silicon Labs", "CP2102N USB to UART Bridge Controller"},
+	{0},
+};
+
+struct libusb_context *usb_ctx;
+static libusb_device_handle *cp210x_handle;
+
+#define CP210X_FAST
+#ifdef CP210X_FAST
+static int cp210x_mosi;
+static int cp210x_sck;
+#endif
+
+static int cp210x_gpio_get()
+{
+	int res;
+	uint8_t gpio;
+
+	res = libusb_control_transfer (
+		cp210x_handle,          // libusb_device_handle *  dev_handle,
+                REQTYPE_DEVICE_TO_HOST, // uint8_t         bmRequestType,
+                CP210X_VENDOR_SPECIFIC, // uint8_t         bRequest,
+                CP210X_READ_LATCH,      // uint16_t        wValue,
+                0,                      // uint16_t        wIndex,
+                &gpio,                  // unsigned char * data,
+                1,                      // uint16_t        wLength,
+                0);                     // unsigned int    timeout
+
+	if (res < 0) {
+		msg_perr("Failed to read GPIO pins (%s)\n", libusb_error_name(res));
+		return 0;
+	}
+
+	return gpio;
+}
+
+static void cp210x_gpio_set(uint8_t val, uint8_t mask)
+{
+	int res;
+	uint16_t gpio;
+
+	gpio = ((val & 0xf) << 8) | (mask & 0xf);
+
+	/* Set relay state on the card */
+	res = libusb_control_transfer (
+                cp210x_handle,          // libusb_device_handle *  dev_handle,
+                REQTYPE_HOST_TO_DEVICE, // uint8_t         bmRequestType,
+                CP210X_VENDOR_SPECIFIC, // uint8_t         bRequest,
+                CP210X_WRITE_LATCH,     // uint16_t        wValue,
+                gpio,                   // uint16_t        wIndex,
+                NULL,                   // unsigned char * data,
+                0,                      // uint16_t        wLength,
+                0);                     // unsigned int    timeout
+
+	if (res < 0)
+		msg_perr("Failed to read GPIO pins (%s)\n", libusb_error_name(res));
+}
+
+static void cp210x_bitbang_set_cs(int val)
+{
+	msg_pspew(">><<: %s(%d)\n", __FUNCTION__, val);
+
+#ifdef CP210X_FAST
+	/* evict any cached state before updating chip select */
+	cp210x_gpio_set(cp210x_mosi << DEVELOPERBOX_SPI_MOSI |
+			    cp210x_sck << DEVELOPERBOX_SPI_SCK,
+			1 << DEVELOPERBOX_SPI_MOSI | 1 << DEVELOPERBOX_SPI_SCK);
+#endif
+
+	cp210x_gpio_set((!!val) << DEVELOPERBOX_SPI_CS, 1 << DEVELOPERBOX_SPI_CS);
+}
+
+static void cp210x_bitbang_set_sck(int val)
+{
+	msg_pspew(">><<: %s(%d)\n", __FUNCTION__, val);
+#ifndef CP210X_FAST
+	cp210x_gpio_set((!!val) << DEVELOPERBOX_SPI_SCK, 1 << DEVELOPERBOX_SPI_SCK);
+#else
+	/* evict any previous falling edge */
+	if (!cp210x_sck)
+		cp210x_gpio_set(cp210x_mosi << DEVELOPERBOX_SPI_MOSI,
+				1 << DEVELOPERBOX_SPI_MOSI | 1 << DEVELOPERBOX_SPI_SCK);
+
+	cp210x_sck = !!val;
+
+	/* send a rising edge */
+	if (cp210x_sck)
+		cp210x_gpio_set(cp210x_sck << DEVELOPERBOX_SPI_SCK,
+				1 << DEVELOPERBOX_SPI_SCK);
+#endif
+}
+
+static void cp210x_bitbang_set_mosi(int val)
+{
+	msg_pspew(">><<: %s(%d)\n", __FUNCTION__, val);
+#ifndef CP210X_FAST
+	cp210x_gpio_set((!!val) << DEVELOPERBOX_SPI_MOSI, 1 << DEVELOPERBOX_SPI_MOSI);
+#else
+	cp210x_mosi = !!val;
+#endif
+}
+
+static int cp210x_bitbang_get_miso(void)
+{
+	msg_pspew(">><<: %s\n", __FUNCTION__);
+	return !!(cp210x_gpio_get() & (1 << DEVELOPERBOX_SPI_MISO));
+}
+
+static const struct bitbang_spi_master bitbang_spi_master_cp210x = {
+	.type = BITBANG_SPI_MASTER_DEVELOPERBOX,
+	.set_cs = cp210x_bitbang_set_cs,
+	.set_sck = cp210x_bitbang_set_sck,
+	.set_mosi = cp210x_bitbang_set_mosi,
+	.get_miso = cp210x_bitbang_get_miso,
+	.half_period = 0,
+};
+
+/* Might be useful for other USB devices as well. static for now.
+ * num parameter allows user to specify one device of multiple installed */
+static struct libusb_device_handle *get_device_by_vid_pid_number(uint16_t vid, uint16_t pid, unsigned int num)
+{
+	struct libusb_device **list;
+	ssize_t count = libusb_get_device_list(usb_ctx, &list);
+	if (count < 0) {
+		msg_perr("Getting the USB device list failed (%s)!\n", libusb_error_name(count));
+		return NULL;
+	}
+
+	struct libusb_device_handle *handle = NULL;
+	ssize_t i = 0;
+	for (i = 0; i < count; i++) {
+		struct libusb_device *dev = list[i];
+		struct libusb_device_descriptor desc;
+		int err = libusb_get_device_descriptor(dev, &desc);
+		if (err != 0) {
+			msg_perr("Reading the USB device descriptor failed (%s)!\n", libusb_error_name(err));
+			libusb_free_device_list(list, 1);
+			return NULL;
+		}
+		if ((desc.idVendor == vid) && (desc.idProduct == pid)) {
+			msg_pdbg("Found USB device %04"PRIx16":%04"PRIx16" at address %d-%d.\n",
+				 desc.idVendor, desc.idProduct,
+				 libusb_get_bus_number(dev), libusb_get_device_address(dev));
+			if (num == 0) {
+				err = libusb_open(dev, &handle);
+				if (err != 0) {
+					msg_perr("Opening the USB device failed (%s)!\n",
+						 libusb_error_name(err));
+					libusb_free_device_list(list, 1);
+					return NULL;
+				}
+				break;
+			}
+			num--;
+		}
+	}
+	libusb_free_device_list(list, 1);
+
+	return handle;
+}
+
+static int developerbox_spi_shutdown(void *data)
+{
+	libusb_close(cp210x_handle);
+	libusb_exit(usb_ctx);
+
+	return 0;
+}
+
+int developerbox_spi_init()
+{
+	char *device;
+	long usedevice = 0;
+
+	device = extract_programmer_param("device");
+	if (device) {
+		char *dev_suffix;
+		errno = 0;
+		usedevice = strtol(device, &dev_suffix, 10);
+		if (errno != 0 || device == dev_suffix) {
+			msg_perr("Error: Could not convert 'device'.\n");
+			free(device);
+			return 1;
+		}
+		if (usedevice < 0 || usedevice > UINT_MAX) {
+			msg_perr("Error: Value for 'device' is out of range.\n");
+			free(device);
+			return 1;
+		}
+		if (strlen(dev_suffix) > 0) {
+			msg_perr("Error: Garbage following 'device' value.\n");
+			free(device);
+			return 1;
+		}
+		msg_pinfo("Using device %li.\n", usedevice);
+	}
+	free(device);
+
+	/* Here comes the USB stuff. */
+	libusb_init(&usb_ctx);
+	if (!usb_ctx) {
+		msg_perr("Could not initialize libusb!\n");
+		return 1;
+	}
+
+	const uint16_t vid = devs_developerbox[0].vendor_id;
+	const uint16_t pid = devs_developerbox[0].device_id;
+	cp210x_handle = get_device_by_vid_pid_number(vid, pid, (unsigned int) usedevice);
+	if (!cp210x_handle) {
+		msg_perr("Could not find a Developerbox programmer on USB.\n");
+		libusb_exit(usb_ctx);
+		return 1;
+	}
+
+	if (register_shutdown(developerbox_spi_shutdown, NULL))
+		return 1;
+
+	if (register_spi_bitbang_master(&bitbang_spi_master_cp210x)) {
+		/* This should never happen. */
+		msg_perr("Developerbox bitbang SPI master init failed!\n");
+		return 1;
+	}
+
+	return 0;
+}
diff --git a/flashrom.c b/flashrom.c
index f85dbb1f836c..6f8fc740d85b 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -255,6 +255,18 @@  const struct programmer_entry programmer_table[] = {
 	},
 #endif
 
+#if CONFIG_DEVELOPERBOX == 1
+	{
+		.name			= "developerbox",
+		.type			= USB,
+		.devs.dev		= devs_developerbox,
+		.init			= developerbox_spi_init,
+		.map_flash_region	= fallback_map,
+		.unmap_flash_region	= fallback_unmap,
+		.delay			= internal_delay,
+	},
+#endif
+
 #if CONFIG_RAYER_SPI == 1
 	{
 		.name			= "rayer_spi",
diff --git a/programmer.h b/programmer.h
index e49f2621ea1e..e0cf8ca759fe 100644
--- a/programmer.h
+++ b/programmer.h
@@ -73,6 +73,9 @@  enum programmer {
 #if CONFIG_DEDIPROG == 1
 	PROGRAMMER_DEDIPROG,
 #endif
+#if CONFIG_DEVELOPERBOX == 1
+	PROGRAMMER_DEVELOPERBOX,
+#endif
 #if CONFIG_RAYER_SPI == 1
 	PROGRAMMER_RAYER_SPI,
 #endif
@@ -169,6 +172,9 @@  enum bitbang_spi_master_type {
 #if CONFIG_OGP_SPI == 1
 	BITBANG_SPI_MASTER_OGP,
 #endif
+#if CONFIG_DEVELOPERBOX == 1
+	BITBANG_SPI_MASTER_DEVELOPERBOX,
+#endif
 };
 
 struct bitbang_spi_master {
@@ -542,6 +548,12 @@  int dediprog_init(void);
 extern const struct dev_entry devs_dediprog[];
 #endif
 
+/* developerbox_spi.c */
+#if CONFIG_DEVELOPERBOX == 1
+int developerbox_spi_init(void);
+extern const struct dev_entry devs_developerbox[];
+#endif
+
 /* ch341a_spi.c */
 #if CONFIG_CH341A_SPI == 1
 int ch341a_spi_init(void);