1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-04-26 22:52:34 +02:00

Add support for the MSTAR I2C ISP protocol

Basically, among other chips, MSTAR manufactures SoCs that equip TV sets
and computer screens, and it seems that all of their products use the
same in-system programming protocol. Basically, they use the DDC channel
of VGA or DVI connectors, which is actually an I2C bus, to encapsulate
SPI frames (the flash chip is connected to the SoC through an SPI bus).

I wrote this patch since the screen I bought had a software bug, and the
manufacturer only released a new firmware binary, but no tool or
instructions on flashing it.

More details can be found here:
http://boeglin.org/blog/index.php?entry=Flashing-a-BenQ-Z-series-for-free(dom)

I only read code from Linux kernel archives published by Acer to figure
out the protocol (for a touchscreen controller and an NFC chip, both by
MSTAR, that share the same ISP protocol), so I don't think there are
any legal problems with it.

Compilation is currently disabled by default in the Makefile.
If in doubt, additional Makefile bugs were added by Stefan.

Corresponding to flashrom svn r1860.

Signed-off-by: Alexandre Boeglin <alex@boeglin.org>
Signed-off-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
Acked-by: Stefan Tauner <stefan.tauner@alumni.tuwien.ac.at>
This commit is contained in:
Alexandre Boeglin 2014-12-20 20:25:19 +00:00 committed by Stefan Tauner
parent 5859ced80f
commit 80e6471588
5 changed files with 339 additions and 0 deletions

View File

@ -300,6 +300,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
###############################################################################
@ -413,6 +418,9 @@ CONFIG_FT2232_SPI ?= yes
# Always enable Altera USB-Blaster dongles for now.
CONFIG_USBBLASTER_SPI ?= yes
# MSTAR DDC support needs more tests/reviews/cleanups.
CONFIG_MSTARDDC_SPI ?= no
# Always enable dummy tracing for now.
CONFIG_DUMMY ?= yes
@ -670,6 +678,13 @@ FEATURE_CFLAGS += $(shell LC_ALL=C grep -q "LINUX_SPI_SUPPORT := yes" .features
PROGRAMMER_OBJS += linux_spi.o
endif
ifeq ($(CONFIG_MSTARDDC_SPI), yes)
# This is a totally ugly hack.
FEATURE_CFLAGS += $(shell LC_ALL=C grep -q "LINUX_I2C_SUPPORT := yes" .features && printf "%s" "-D'CONFIG_MSTARDDC_SPI=1'")
NEED_LINUX_I2C := yes
PROGRAMMER_OBJS += mstarddc_spi.o
endif
ifeq ($(NEED_SERIAL), yes)
LIB_OBJS += serial.o
endif
@ -927,6 +942,19 @@ int main(int argc, char **argv)
endef
export LINUX_SPI_TEST
define LINUX_I2C_TEST
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
int main(int argc, char **argv)
{
(void) argc;
(void) argv;
return 0;
}
endef
export LINUX_I2C_TEST
features: compiler
@echo "FEATURES := yes" > .features.tmp
ifeq ($(NEED_FTDI), yes)
@ -947,6 +975,13 @@ ifeq ($(CONFIG_LINUX_SPI), yes)
@$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) .featuretest.c -o .featuretest$(EXEC_SUFFIX) >/dev/null 2>&1 && \
( echo "yes."; echo "LINUX_SPI_SUPPORT := yes" >> .features.tmp ) || \
( echo "no."; echo "LINUX_SPI_SUPPORT := no" >> .features.tmp )
endif
ifeq ($(NEED_LINUX_I2C), yes)
@printf "Checking if Linux I2C headers are present... "
@echo "$$LINUX_I2C_TEST" > .featuretest.c
@$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) .featuretest.c -o .featuretest$(EXEC_SUFFIX) >/dev/null 2>&1 && \
( echo "yes."; echo "LINUX_I2C_SUPPORT := yes" >> .features.tmp ) || \
( echo "no."; echo "LINUX_I2C_SUPPORT := no" >> .features.tmp )
endif
@printf "Checking for utsname support... "
@echo "$$UTSNAME_TEST" > .featuretest.c

View File

@ -227,6 +227,8 @@ bitbanging adapter)
.sp
.BR "* nicintel_eeprom" " (for SPI EEPROMs on Intel Gigabit network cards)"
.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 INFORMATION
@ -889,6 +891,51 @@ 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 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.
This flashrom module allows the latter via Linux's I2C driver.
.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 connected 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

View File

@ -354,6 +354,18 @@ const struct programmer_entry programmer_table[] = {
},
#endif
#if CONFIG_MSTARDDC_SPI == 1
{
.name = "mstarddc_spi",
.type = OTHER,
.devs.note = "MSTAR DDC devices addressable via /dev/i2c-* on Linux.\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. */
};

234
mstarddc_spi.c Normal file
View File

@ -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_master spi_master_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;
uint8_t 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_master(&spi_master_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_master spi_master_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

View File

@ -98,6 +98,9 @@ enum programmer {
#endif
#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. */
};
@ -474,6 +477,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);
@ -555,6 +563,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