Add support for flashing using CD/DVD drives with mediatek chipsets.
As the driver is now, it only supports parallel ATA drives with parallel
flash chips. The kind of drive access needed is not possible through IDE
drivers in the OS, flashrom has to access the IDE port directly. This will
interfere with OS drivers for the drive, so you better make sure that
the OS does not access the drive (or another drive on the same PATA channel)
during the flash process.
On recent linux kernels (with libata), the invocation
"-p mediatek_pata:devname=sr0" should work. It is supposed to forcibly
unregister all drives on the IDE channel sr0 is on (you better don't have
a file system mounted on a hard drive on the same channel), perform the
flash job, and, if the drive successfully reboots, re-add all detectable
IDE devices on that channel.
This patch is known to lack a man page section and violate the flashrom
coding style in some places - I send it out to catch some early reviews
on it.
Signed-off-by: Michael Karcher <flashrom(a)mkarcher.dialup.fu-berlin.de>
---
Makefile | 14 +++
flashrom.c | 24 ++++-
mediatek.c | 350 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
programmer.h | 8 ++
4 files changed, 395 insertions(+), 1 deletions(-)
create mode 100644 mediatek.c
diff --git a/Makefile b/Makefile
index 6028485..a38740f 100644
--- a/Makefile
+++ b/Makefile
@@ -158,6 +158,9 @@ CONFIG_DEDIPROG ?= no
# Always enable Marvell SATA controllers for now.
CONFIG_SATAMV ?= yes
+# Disable Mediatek PATA programmer until cleaned up
+CONFIG_MEDIATEK_PATA ?= no
+
# Disable wiki printing by default. It is only useful if you have wiki access.
CONFIG_PRINT_WIKI ?= no
@@ -292,6 +295,17 @@ PROGRAMMER_OBJS += satamv.o
NEED_PCI := yes
endif
+ifeq ($(CONFIG_MEDIATEK_PATA), yes)
+FEATURE_CFLAGS += -D'CONFIG_MEDIATEK_PATA=1'
+PROGRAMMER_OBJS += mediatek.o
+# actually, I/O access, not PCI
+NEED_PCI := yes
+ifeq ($(OS_ARCH), Linux)
+LIBS += $(shell pkg-config --libs libsysfs)
+FEATURE_CFLAGS += $(shell pkg-config --cflags libsysfs)
+endif
+endif
+
ifeq ($(NEED_SERIAL), yes)
LIB_OBJS += serial.o
endif
diff --git a/flashrom.c b/flashrom.c
index 4c6c1fa..05c0a0b 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -52,7 +52,7 @@ enum programmer programmer = PROGRAMMER_DUMMY;
* if more than one of them is selected. If only one is selected, it is clear
* that the user wants that one to become the default.
*/
-#if CONFIG_NIC3COM+CONFIG_NICREALTEK+CONFIG_NICNATSEMI+CONFIG_GFXNVIDIA+CONFIG_DRKAISER+CONFIG_SATASII+CONFIG_ATAHPT+CONFIG_FT2232_SPI+CONFIG_SERPROG+CONFIG_BUSPIRATE_SPI+CONFIG_DEDIPROG+CONFIG_RAYER_SPI+CONFIG_NICINTEL_SPI+CONFIG_OGP_SPI+CONFIG_SATAMV > 1
+#if CONFIG_NIC3COM+CONFIG_NICREALTEK+CONFIG_NICNATSEMI+CONFIG_GFXNVIDIA+CONFIG_DRKAISER+CONFIG_SATASII+CONFIG_ATAHPT+CONFIG_FT2232_SPI+CONFIG_SERPROG+CONFIG_BUSPIRATE_SPI+CONFIG_DEDIPROG+CONFIG_RAYER_SPI+CONFIG_NICINTEL_SPI+CONFIG_OGP_SPI+CONFIG_SATAMV+CONFIG_MEDIATEK_PATA > 1
#error Please enable either CONFIG_DUMMY or CONFIG_INTERNAL or disable support for all programmers except one.
#endif
enum programmer programmer =
@@ -102,6 +102,9 @@ enum programmer programmer =
#if CONFIG_SATAMV == 1
PROGRAMMER_SATAMV
#endif
+#if CONFIG_MEDIATEK_PATA == 1
+ PROGRAMMER_MEDIATEK_PATA
+#endif
;
#endif
@@ -483,6 +486,25 @@ const struct programmer_entry programmer_table[] = {
},
#endif
+#if CONFIG_MEDIATEK_PATA == 1
+ {
+ .name = "mediatek_pata",
+ .init = mediatek_pata_init,
+ .shutdown = mediatek_pata_shutdown,
+ .map_flash_region = fallback_map,
+ .unmap_flash_region = fallback_unmap,
+ .chip_readb = mediatek_pata_chip_readb,
+ .chip_readw = fallback_chip_readw,
+ .chip_readl = fallback_chip_readl,
+ .chip_readn = fallback_chip_readn,
+ .chip_writeb = mediatek_pata_chip_writeb,
+ .chip_writew = fallback_chip_writew,
+ .chip_writel = fallback_chip_writel,
+ .chip_writen = fallback_chip_writen,
+ .delay = internal_delay,
+ },
+#endif
+
{}, /* This entry corresponds to PROGRAMMER_INVALID. */
};
diff --git a/mediatek.c b/mediatek.c
new file mode 100644
index 0000000..e44a261
--- /dev/null
+++ b/mediatek.c
@@ -0,0 +1,350 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2011 Michael Karcher
+ *
+ * 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
+ */
+
+/* driver for PATA connected CD/DVD drives with mediatek chipset */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "flash.h"
+#include "programmer.h"
+
+#ifdef __linux__
+#include <libsysfs.h>
+
+static int grab_device(const char *devname, unsigned * portnum, int * isslave, char * rescanpath)
+{
+ struct sysfs_class *blockclass;
+ struct sysfs_class_device *cdclassdevice;
+ struct sysfs_device *cddevice, *targetdevice, *hostdevice, *atacontroller;
+ struct dlist *targets, *hostadapters, *hosts;
+ const char *subtarget, *haname;
+ const char *resourcelist;
+ int devhostnum, hostnum;
+ int hostbus = 0, i;
+ blockclass = sysfs_open_class("block");
+ cdclassdevice = sysfs_get_class_device(blockclass,devname);
+ if (!cdclassdevice)
+ {
+ fputs("Device not found\n", stderr);
+ return -1;
+ }
+ cddevice = sysfs_get_classdev_device(cdclassdevice);
+ /* should be only 0 or 1 */
+ if (sscanf(cddevice->name,"%*d:%*d:%d",isslave) != 1)
+ {
+ fputs("can't parse device ID\n", stderr);
+ return -1;
+ }
+ targetdevice = sysfs_get_device_parent(cddevice);
+ hostdevice = sysfs_get_device_parent(targetdevice);
+ sprintf(rescanpath,"%s/scsi_host",hostdevice->path);
+ hostadapters = sysfs_open_directory_list(rescanpath);
+ dlist_for_each_data(hostadapters, haname, const char) break;
+ sprintf(rescanpath,"%s/scsi_host/%s/scan",hostdevice->path,haname);
+ sysfs_close_list(hostadapters);
+ targets = sysfs_open_directory_list(hostdevice->path);
+ dlist_for_each_data (targets, subtarget, const char) {
+ /* handle all targets on that bus */
+ if (strncmp(subtarget,"target",6) == 0)
+ {
+ struct dlist * luns;
+ const char * lundevname;
+ char lunspath[SYSFS_PATH_MAX], deldevpath[SYSFS_PATH_MAX];
+ sprintf(lunspath, "%s/%s", hostdevice->path, subtarget);
+ /* handle all (i.e. one) LUN on that target */
+ luns = sysfs_open_directory_list(lunspath);
+ dlist_for_each_data (luns, lundevname, const char) {
+ /* handle children, not the "power" subdir */
+ if (strchr(lundevname,':'))
+ {
+ FILE * f;
+ sprintf(deldevpath, "%s/%s/delete", lunspath, lundevname);
+ f = fopen(deldevpath,"w");
+ if (!f)
+ {
+ fprintf(stderr, "Can't open %s for grabbing device\n", deldevpath);
+ return -1;
+ }
+ fputs("1\n", f);
+ fclose(f);
+ }
+ }
+ sysfs_close_list(luns);
+ }
+ }
+ sysfs_close_list(targets);
+ if (sscanf(hostdevice->name, "host%d", &devhostnum) != 1)
+ {
+ fputs("Can't parse host number\n",stderr);
+ return -1;
+ }
+ atacontroller = sysfs_get_device_parent(hostdevice);
+ hosts = sysfs_open_directory_list(atacontroller->path);
+ dlist_for_each_data (hosts, haname, const char) {
+ if (sscanf(haname, "host%d", &hostnum) == 1 &&
+ hostnum < devhostnum)
+ hostbus++;
+ }
+ sysfs_close_list(hosts);
+ resourcelist = sysfs_get_device_attr(atacontroller, "resource")->value;
+ for (i = 0; i < 2*hostbus; i++)
+ resourcelist = strchr(resourcelist, '\n') + 1;
+ if (sscanf(resourcelist,"%i",portnum) != 1)
+ {
+ fputs("Can't parse port number\n", stderr);
+ return 1;
+ }
+ sysfs_close_class(blockclass);
+ return 0;
+}
+
+static void release_device(void *rescanpath)
+{
+ FILE * f;
+ f = fopen((const char*)rescanpath, "w");
+ if (!f)
+ {
+ fputs("Can't open rescanning metafile\n", stderr);
+ return;
+ }
+ fputs("- - -\n",f);
+ fclose(f);
+}
+
+#endif
+
+struct ataintf {
+ unsigned portbase;
+ int slavebit;
+} mtk_intf;
+
+static int ide_submit(struct ataintf *ata, unsigned char *command)
+{
+ int i, status, ret = 0;
+ command[6] &= ~0x10;
+ command[6] |= ata->slavebit;
+ for (i = 1; i < 7; i++)
+ outb(command[i], ata->portbase+i);
+ outb(command[0], ata->portbase+7);
+ programmer_delay(10000);
+ i = 0;
+ while ((status = inb(ata->portbase+7)) & 0x80 && ++i < 200)
+ programmer_delay(10000);
+ if (i == 200)
+ ret = -1;
+ command[0] = status;
+ for (i = 1; i < 7; i++)
+ command[i] = inb(ata->portbase + i);
+ return ret;
+}
+
+static int mtk_open(unsigned portbase, int is_slave, struct ataintf * ata)
+{
+ ata->portbase = portbase;
+ if (is_slave)
+ ata->slavebit = 0x10;
+ else
+ ata->slavebit = 0;
+ return 0;
+}
+
+#define MTK_A19toA17 1
+#define MTK_CONTROL 2
+#define MTK_DATA 3
+#define MTK_A7toA0 4
+#define MTK_A15toA8 5
+
+#define MTK_CTL_RAISE_nWE 0x01
+#define MTK_CTL_LOWER_nWE 0x02
+#define MTK_CTL_RAISE_nOE 0x04
+#define MTK_CTL_LOWER_nOE 0x08
+#define MTK_CTL_RAISE_nCS 0x10
+#define MTK_CTL_LOWER_nCS 0x20 /* latches low 8 address bits */
+#define MTK_CTL_DRIVE_DATA 0x40
+#define MTK_CTL_ADDRESS_A16 0x80
+
+static int mtk_open_flash(struct ataintf * ata)
+{
+ unsigned char task[7];
+ task[0] = 0x80; /* Vendor specific */
+ task[3] = 0x2A; /* mediatek: start flash access */
+ ide_submit(ata, task);
+ msg_pdbg("status: %x\n", task[0]);
+ if ((task[0] & 0xFC) != 0x70) {
+ msg_perr("Mediatek flash mode signature didn't appear. Most likely "
+ "unsupported device\n");
+ return 1;
+ }
+ if (task[0] == 0x70) /* parallel flash */
+ {
+ outb(MTK_CTL_RAISE_nCS | MTK_CTL_RAISE_nOE | MTK_CTL_RAISE_nWE, ata->portbase + MTK_CONTROL);
+ return 0;
+ }
+ else
+ {
+ msg_perr("Signature for parallel flash not found, maybe SPI flash?\n");
+ return 1;
+ }
+}
+
+static int mtk_close_flash(struct ataintf * ata)
+{
+ unsigned char task[8];
+ task[0] = 0x81; /* Vendor specific */
+ task[2] = 8;
+ task[3] = 0;
+ if (ide_submit(ata, task) == -1)
+ {
+ msg_perr("error rebooting CD drive\n");
+ return -1;
+ }
+ return 0;
+}
+
+static unsigned char mtk_readb(struct ataintf * ata, unsigned address)
+{
+ unsigned char val;
+ unsigned char A16bit = address & 0x10000 ? MTK_CTL_ADDRESS_A16 : 0;
+ outb(A16bit, ata->portbase + MTK_CONTROL);
+ outb(address >> 17, ata->portbase + MTK_A19toA17);
+ outb(address >> 8, ata->portbase + MTK_A15toA8);
+ outb(address >> 0, ata->portbase + MTK_A7toA0);
+ outb(MTK_CTL_LOWER_nCS | MTK_CTL_LOWER_nOE | A16bit, ata->portbase + MTK_CONTROL);
+ val = inb(ata->portbase + MTK_DATA);
+ outb(MTK_CTL_RAISE_nCS | MTK_CTL_RAISE_nOE, ata->portbase + MTK_CONTROL);
+ return val;
+}
+
+static void mtk_writeb(struct ataintf * ata, unsigned char val, unsigned address)
+{
+ unsigned char A16bit = address & 0x10000 ? MTK_CTL_ADDRESS_A16 : 0;
+ outb(A16bit, ata->portbase + MTK_CONTROL);
+ outb(address >> 17, ata->portbase + MTK_A19toA17);
+ outb(address >> 8, ata->portbase + MTK_A15toA8);
+ outb(address >> 0, ata->portbase + MTK_A7toA0);
+ outb(val, ata->portbase + MTK_DATA);
+ outb(MTK_CTL_LOWER_nCS | MTK_CTL_DRIVE_DATA | A16bit, ata->portbase + MTK_CONTROL);
+ outb(MTK_CTL_LOWER_nWE | MTK_CTL_DRIVE_DATA | A16bit, ata->portbase + MTK_CONTROL);
+ outb(MTK_CTL_RAISE_nCS | MTK_CTL_RAISE_nWE, ata->portbase + MTK_CONTROL);
+}
+
+#ifdef __linux__
+static char rescanpath[SYSFS_PATH_MAX] = "";
+#endif
+
+int mediatek_pata_init(void)
+{
+ unsigned iobase;
+ int isslave;
+ char *portpos = NULL, *devpos = NULL;
+
+ /* IDE port specified */
+ if ((portpos = extract_programmer_param("iobase"))) {
+ char *endptr = NULL;
+ char *slaveptr;
+ unsigned long tmp;
+ tmp = strtoul(portpos, &endptr, 0);
+ free(portpos);
+ /* Port 0, port >0x10000, unaligned ports and garbage strings
+ * are rejected.
+ */
+ if (!tmp || (tmp >= 0x10000) || (tmp & 0x7) ||
+ (*endptr != '\0')) {
+ /* Using ports below 0x100 is a really bad idea, and
+ * should only be done if no port between 0x100 and
+ * 0xfffc works due to routing issues.
+ */
+ msg_perr("Error: iobase= specified, but the I/O base "
+ "given was invalid.\nIt must be a multiple of "
+ "0x8 and lie between 0x100 and 0xfff8.\n");
+ return 1;
+ }
+ iobase = tmp;
+ slaveptr = extract_programmer_param("slave");
+ isslave = slaveptr != NULL;
+ free(slaveptr);
+ } else if ((devpos = extract_programmer_param("devname"))) {
+#ifdef __linux__
+ if(grab_device(devpos, &iobase, &isslave, rescanpath) == 0)
+ {
+ msg_pinfo("device grabbed: %s at port %x\n",
+ isslave?"slave":"master", iobase);
+ }
+ else
+ {
+ msg_perr("can't get access to device %s\n", devpos);
+ return -1;
+ }
+#else
+ msg_perr("Open by devname is not implemented on this OS yet\n");
+ return -1;
+#endif
+ } else {
+ msg_perr("Need to specify either iobase or devname for the "
+ "mediatek_pata driver\n");
+ return -1;
+ }
+
+ get_io_perms();
+ if (mtk_open(iobase, isslave, &mtk_intf) < 0)
+ {
+ msg_perr("can't access that device, aborting\n");
+// return -1;
+ }
+ if (mtk_open_flash(&mtk_intf) < 0)
+ {
+ msg_perr("can't get access to flash chip, aborting\n");
+ return -1;
+ }
+ return 0;
+}
+
+void mediatek_pata_chip_writeb(uint8_t val, chipaddr addr)
+{
+ mtk_writeb(&mtk_intf, val, addr);
+}
+
+uint8_t mediatek_pata_chip_readb(const chipaddr addr)
+{
+ return mtk_readb(&mtk_intf, addr);
+}
+
+int mediatek_pata_shutdown(void)
+{
+ int res;
+ res = mtk_close_flash(&mtk_intf);
+#ifdef __linux__
+ if (rescanpath[0]) {
+ if (res >= 0) /* don't rescan if the drive is broken now */
+ release_device(rescanpath);
+ else {
+ msg_perr("probably bad flash contents, won't"
+ " rescan ATA bus\n");
+ msg_perr("use \"echo - - - > %s\" to rescan after"
+ " fixing\n",rescanpath);
+ msg_perr("use \"-p mediatek_pata:iobase=0x%x%s\" until"
+ " the drive is working again\n",
+ mtk_intf.portbase,
+ mtk_intf.slavebit ? ":slave" : "");
+ }
+ }
+ rescanpath[0] = 0;
+#endif
+ return 0;
+}
diff --git a/programmer.h b/programmer.h
index 7a497f8..b62b86f 100644
--- a/programmer.h
+++ b/programmer.h
@@ -82,6 +82,9 @@ enum programmer {
#if CONFIG_SATAMV == 1
PROGRAMMER_SATAMV,
#endif
+#if CONFIG_MEDIATEK_PATA == 1
+ PROGRAMMER_MEDIATEK_PATA,
+#endif
PROGRAMMER_INVALID /* This must always be the last entry. */
};
@@ -516,6 +519,11 @@ char *extract_programmer_param(char *param_name);
/* layout.c */
int show_id(uint8_t *bios, int size, int force);
+int mediatek_pata_init(void);
+int mediatek_pata_shutdown(void);
+void mediatek_pata_chip_writeb(uint8_t val, chipaddr addr);
+uint8_t mediatek_pata_chip_readb(const chipaddr addr);
+
/* spi.c */
enum spi_controller {
SPI_CONTROLLER_NONE,
--
1.7.2.3