Patchwork Add programmer for the MSTAR I2C ISP protocol

login
register
about
Submitter Alexandre Boeglin
Date 2014-05-08 16:28:29
Message ID <20140508162829.GA3337@boeglin.org>
Download mbox | patch
Permalink /patch/4144/
State Accepted
Headers show

Comments

Alexandre Boeglin - 2014-05-08 16:28:29
Le jeudi 08 mai 2014 à 14:01, Alexandre Boeglin a écrit:
> Signed-off-by: Alexandre Boeglin <alex@boeglin.org>

Here's a new revision, removing the useless err variables, as discussed
on IRC.


Alex
Stefan Tauner - 2014-12-20 20:30:54
On Thu, 8 May 2014 18:28:29 +0200
Alexandre Boeglin <alex@boeglin.org> wrote:

> Le jeudi 08 mai 2014 à 14:01, Alexandre Boeglin a écrit:
> > Signed-off-by: Alexandre Boeglin <alex@boeglin.org>
> 
> Here's a new revision, removing the useless err variables, as discussed
> on IRC.

Hey Alexandre,

I have committed a slightly refined version of this patch in r1860.
Additionally to your changes and some rebasing fixes I have added a
compile-time check for the Linux i2c headers to the Makefile. And I
have disabled the compilation of the MSTAR programmer for the time
being due to the open questions and some code quality issues. Anybody
who dares can easily compile it with "make CONFIG_MSTARDDC_SPI=yes" on
Linux.

Thanks again for your patch and background story!

Patch

From 54d6158a3c8a4cda68371db7ca86073183c2e754 Mon Sep 17 00:00:00 2001
From: Alexandre Boeglin <alex@boeglin.org>
Date: Tue, 29 Apr 2014 21:35:26 +0200
Subject: [PATCH] Add programmer for the MSTAR I2C ISP protocol

Signed-off-by: Alexandre Boeglin <alex@boeglin.org>
---
 Makefile        |  13 ++++
 flashrom.8.tmpl |  53 +++++++++++++
 flashrom.c      |  12 +++
 mstarddc_spi.c  | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 programmer.h    |  11 +++
 5 files changed, 323 insertions(+)
 create mode 100644 mstarddc_spi.c

diff --git a/Makefile b/Makefile
index 346d46a..d6e932e 100644
--- a/Makefile
+++ b/Makefile
@@ -280,6 +280,11 @@  UNSUPPORTED_FEATURES += CONFIG_LINUX_SPI=yes
 else
 override CONFIG_LINUX_SPI = no
 endif
+ifeq ($(CONFIG_MSTARDDC_SPI), yes)
+UNSUPPORTED_FEATURES += CONFIG_MSTARDDC_SPI=yes
+else
+override CONFIG_MSTARDDC_SPI = no
+endif
 endif
 
 ###############################################################################
@@ -390,6 +395,9 @@  CONFIG_FT2232_SPI ?= yes
 # Always enable Altera USB-Blaster dongles for now.
 CONFIG_USBBLASTER_SPI ?= yes
 
+# Always enable MSTAR DDC for now.
+CONFIG_MSTARDDC_SPI ?= yes
+
 # Always enable dummy tracing for now.
 CONFIG_DUMMY ?= yes
 
@@ -549,6 +557,11 @@  NEED_FTDI := yes
 PROGRAMMER_OBJS += usbblaster_spi.o
 endif
 
+ifeq ($(CONFIG_MSTARDDC_SPI), yes)
+FEATURE_CFLAGS += -D'CONFIG_MSTARDDC_SPI=1'
+PROGRAMMER_OBJS += mstarddc_spi.o
+endif
+
 ifeq ($(NEED_FTDI), yes)
 FTDILIBS := $(shell pkg-config --libs libftdi 2>/dev/null || printf "%s" "-lftdi -lusb")
 FEATURE_CFLAGS += $(shell LC_ALL=C grep -q "FT232H := yes" .features && printf "%s" "-D'HAVE_FT232H=1'")
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 8d46ffe..3948a39 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -221,6 +221,8 @@  bitbanging adapter)
 .sp
 .BR "* usbblaster_spi" " (for SPI flash ROMs attached to an Altera USB-Blaster compatible cable)"
 .sp
+.BR "* mstarddc_spi" " (for SPI flash ROMs accessible through DDC in MSTAR-equipped displays)"
+.sp
 Some programmers have optional or mandatory parameters which are described
 in detail in the
 .B PROGRAMMER SPECIFIC INFO
@@ -842,6 +844,57 @@  Example that sets the frequency to 8 MHz:
 .B "  flashrom \-p linux_spi:dev=/dev/spidevX.Y,spispeed=8000"
 .sp
 Please note that the linux_spi driver only works on Linux.
+.SS
+.BR "mstarddc_spi " programmer
+The Display Data Channel (DDC) is an I2C bus present on VGA and DVI
+connectors, that allows exchanging informations between a computer and its
+attached displays. Its most common uses are getting display capabilities
+through EDID (at I2C address 0x50) and sending commands to the display using
+the DDC/CI protocol (at address 0x37). On displays driven by MSTAR SoCs, it is
+also possible to access the SoC firmware flash (connected to the Soc through
+another SPI bus) using an In-System Programming (ISP) port, usually at address
+0x49.
+.sp
+.B IMPORTANT:
+Before using this programmer, the display
+.B MUST
+be in standby mode, and only connected to the computer that will run flashrom
+using a VGA cable, to an inactive VGA output. It absolutely
+.B MUST NOT
+be used as a display during the procedure!
+.sp
+You have to specify the DDC/I2C controller and I2C address to use with the
+.sp
+.B "  flashrom \-p mstarddc_spi:dev=/dev/i2c-X:YY"
+.sp
+syntax where
+.B /dev/i2c-X
+is the Linux device node for your I2C controller conected to the display's DDC
+channel, and
+.B YY
+is the (hexadecimal) address of the MSTAR ISP port (address 0x49 is usually
+used).
+Example that uses I2C controller /dev/i2c-1 and address 0x49:
+.sp
+.B "  flashrom \-p mstarddc_spi:dev=/dev/i2c-1:49
+.sp
+It is also possible to inhibit the reset command that is normally sent to the
+display once the flashrom operation is completed using the optional
+.B noreset
+parameter. A value of 1 prevents flashrom from sending the reset command.
+Example that does not reset the display at the end of the operation:
+.sp
+.B "  flashrom \-p mstarddc_spi:dev=/dev/i2c-1:49,noreset=1
+.sp
+Please note that sending the reset command is also inhibited in the event an
+error occured during the operation. To send the reset command afterwards, you
+can simply run flashrom once more, in chip probe mode (not specifying an
+operation), without the
+.B noreset
+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.
 .SH EXAMPLES
 To back up and update your BIOS, run
 .sp
diff --git a/flashrom.c b/flashrom.c
index c20461a..0b11468 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -321,6 +321,18 @@  const struct programmer_entry programmer_table[] = {
 	},
 #endif
 
+#if CONFIG_MSTARDDC_SPI == 1
+	{
+		.name			= "mstarddc_spi",
+		.type			= OTHER,
+		.devs.note		= "Device files /dev/i2c-*\n",
+		.init			= mstarddc_spi_init,
+		.map_flash_region	= fallback_map,
+		.unmap_flash_region	= fallback_unmap,
+		.delay			= internal_delay,
+	},
+#endif
+
 	{0}, /* This entry corresponds to PROGRAMMER_INVALID. */
 };
 
diff --git a/mstarddc_spi.c b/mstarddc_spi.c
new file mode 100644
index 0000000..97431ea
--- /dev/null
+++ b/mstarddc_spi.c
@@ -0,0 +1,234 @@ 
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2014 Alexandre Boeglin <alex@boeglin.org>
+ *
+ * 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.
+ *
+ * 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
+ */
+
+#if CONFIG_MSTARDDC_SPI == 1
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include "flash.h"
+#include "programmer.h"
+#include "spi.h"
+
+static const struct spi_programmer spi_programmer_mstarddc;
+
+static int mstarddc_fd;
+static int mstarddc_addr;
+static int mstarddc_doreset = 1;
+
+// MSTAR DDC Commands
+#define MSTARDDC_SPI_WRITE	0x10
+#define MSTARDDC_SPI_READ	0x11
+#define MSTARDDC_SPI_END	0x12
+#define MSTARDDC_SPI_RESET	0x24
+
+/* Returns 0 upon success, a negative number upon errors. */
+static int mstarddc_spi_shutdown(void *data)
+{
+	// Reset, disables ISP mode
+	if (mstarddc_doreset == 1) {
+		uint8_t cmd = MSTARDDC_SPI_RESET;
+		if (write(mstarddc_fd, &cmd, 1) < 0) {
+			msg_perr("Error sending reset command: errno %d.\n",
+				 errno);
+			return -1;
+		}
+	} else {
+		msg_pinfo("Info: Reset command was not sent. "
+			  "Either the noreset=1 option was used, "
+			  "or an error occured.\n");
+	}
+
+	if (close(mstarddc_fd) < 0) {
+		msg_perr("Error closing device: errno %d.\n", errno);
+		return -1;
+	}
+	return 0;
+}
+
+/* Returns 0 upon success, a negative number upon errors. */
+int mstarddc_spi_init(void)
+{
+	char *i2c_device;
+	char *i2c_address;
+	char *noreset;
+
+	// Get device, address from command-line
+	i2c_device = extract_programmer_param("dev");
+	if (i2c_device && strlen(i2c_device)) {
+		if ((i2c_address = strchr(i2c_device, ':'))) {
+			*i2c_address = '\0';
+			i2c_address++;
+		}
+		if (!i2c_address || !strlen(i2c_address)) {
+			msg_perr("Error: no address specified.\n"
+				 "Use flashrom -p mstarddc_spi:dev=/dev/device:address.\n");
+			return -1;
+		}
+		mstarddc_addr = strtol(i2c_address, NULL, 16);
+	} else {
+		msg_perr("Error: no device specified.\n"
+			 "Use flashrom -p mstarddc_spi:dev=/dev/device:address.\n");
+		return -1;
+	}
+	msg_pinfo("Info: Will try to use device %s and address 0x%02x.\n",
+		  i2c_device, mstarddc_addr);
+
+	// Get noreset=1 option from command-line
+	if ((noreset = extract_programmer_param("noreset"))
+	    && noreset[0] == '1')
+		mstarddc_doreset = 0;
+	msg_pinfo("Info: WILL %sreset the device at the end.\n",
+		  mstarddc_doreset ? "" : "NOT ");
+	// Open device
+	if ((mstarddc_fd = open(i2c_device, O_RDWR)) < 0) {
+		switch (errno) {
+		case EACCES:
+			msg_perr("Error opening %s: Permission denied.\n"
+				 "Please use sudo or run as root.\n",
+				 i2c_device);
+			break;
+		case ENOENT:
+			msg_perr("Error opening %s: No such file.\n"
+				 "Please check you specified the correct device.\n",
+				 i2c_device);
+			break;
+		default:
+			msg_perr("Error opening %s: errno %d.\n",
+				 i2c_device, errno);
+		}
+		return -1;
+	}
+	// Set slave address
+	if (ioctl(mstarddc_fd, I2C_SLAVE, mstarddc_addr) < 0) {
+		msg_perr("Error setting slave address 0x%02x: errno %d.\n",
+			 mstarddc_addr, errno);
+		return -1;
+	}
+	// Enable ISP mode
+	uint8_t cmd[5] = { 'M', 'S', 'T', 'A', 'R' };
+	if (write(mstarddc_fd, cmd, 5) < 0) {
+		int enable_err = errno;
+		char end_cmd = MSTARDDC_SPI_END;
+
+		// Assume device is already in ISP mode, try to send END command
+		if (write(mstarddc_fd, &end_cmd, 1) < 0) {
+			msg_perr("Error enabling ISP mode: errno %d & %d.\n"
+				 "Please check that device (%s) and address (0x%02x) are correct.\n",
+				 enable_err, errno, i2c_device, mstarddc_addr);
+			return -1;
+		}
+	}
+	// Register shutdown function
+	register_shutdown(mstarddc_spi_shutdown, NULL);
+
+	// Register programmer
+	register_spi_programmer(&spi_programmer_mstarddc);
+	return 0;
+}
+
+/* Returns 0 upon success, a negative number upon errors. */
+static int mstarddc_spi_send_command(struct flashctx *flash,
+				     unsigned int writecnt,
+				     unsigned int readcnt,
+				     const unsigned char *writearr,
+				     unsigned char *readarr)
+{
+	int ret = 0;
+	uint8_t *cmd;
+
+	if ((cmd = malloc((writecnt + 1) * sizeof(uint8_t))) < 0) {
+		msg_perr("Error allocating memory: errno %d.\n", errno);
+		ret = -1;
+	}
+
+	if (!ret && writecnt) {
+		cmd[0] = MSTARDDC_SPI_WRITE;
+		memcpy(cmd + 1, writearr, writecnt);
+		if (write(mstarddc_fd, cmd, writecnt + 1) < 0) {
+			msg_perr("Error sending write command: errno %d.\n",
+				 errno);
+			ret = -1;
+		}
+	}
+
+	if (!ret && readcnt) {
+		struct i2c_rdwr_ioctl_data i2c_data;
+		struct i2c_msg msg[2];
+
+		cmd[0] = MSTARDDC_SPI_READ;
+		i2c_data.nmsgs = 2;
+		i2c_data.msgs = msg;
+		i2c_data.msgs[0].addr = mstarddc_addr;
+		i2c_data.msgs[0].len = 1;
+		i2c_data.msgs[0].flags = 0;
+		i2c_data.msgs[0].buf = cmd;
+		i2c_data.msgs[1].addr = mstarddc_addr;
+		i2c_data.msgs[1].len = readcnt;
+		i2c_data.msgs[1].flags = I2C_M_RD;
+		i2c_data.msgs[1].buf = readarr;
+
+		if (ioctl(mstarddc_fd, I2C_RDWR, &i2c_data) < 0) {
+			msg_perr("Error sending read command: errno %d.\n",
+				 errno);
+			ret = -1;
+		}
+	}
+
+	if (!ret && (writecnt || readcnt)) {
+		cmd[0] = MSTARDDC_SPI_END;
+		if (write(mstarddc_fd, cmd, 1) < 0) {
+			msg_perr("Error sending end command: errno %d.\n",
+				 errno);
+			ret = -1;
+		}
+	}
+
+	/* Do not reset if something went wrong, as it might prevent from
+	 * retrying flashing. */
+	if (ret != 0)
+		mstarddc_doreset = 0;
+
+	if (cmd)
+		free(cmd);
+
+	return ret;
+}
+
+static const struct spi_programmer spi_programmer_mstarddc = {
+	.type = SPI_CONTROLLER_MSTARDDC,
+	.max_data_read = 256,
+	.max_data_write = 256,
+	.command = mstarddc_spi_send_command,
+	.multicommand = default_spi_send_multicommand,
+	.read = default_spi_read,
+	.write_256 = default_spi_write_256,
+	.write_aai = default_spi_write_aai,
+};
+
+#endif
diff --git a/programmer.h b/programmer.h
index 4f4cc02..9aa2c8d 100644
--- a/programmer.h
+++ b/programmer.h
@@ -90,6 +90,9 @@  enum programmer {
 #if CONFIG_USBBLASTER_SPI == 1
 	PROGRAMMER_USBBLASTER_SPI,
 #endif
+#if CONFIG_MSTARDDC_SPI == 1
+	PROGRAMMER_MSTARDDC_SPI,
+#endif
 	PROGRAMMER_INVALID /* This must always be the last entry. */
 };
 
@@ -446,6 +449,11 @@  int usbblaster_spi_init(void);
 extern const struct dev_entry devs_usbblasterspi[];
 #endif
 
+/* mstarddc_spi.c */
+#if CONFIG_MSTARDDC_SPI == 1
+int mstarddc_spi_init(void);
+#endif
+
 /* rayer_spi.c */
 #if CONFIG_RAYER_SPI == 1
 int rayer_spi_init(void);
@@ -527,6 +535,9 @@  enum spi_controller {
 #if CONFIG_USBBLASTER_SPI == 1
 	SPI_CONTROLLER_USBBLASTER,
 #endif
+#if CONFIG_MSTARDDC_SPI == 1
+	SPI_CONTROLLER_MSTARDDC,
+#endif
 };
 
 #define MAX_DATA_UNSPECIFIED 0
-- 
1.9.2