/*
 * This file is part of the flashrom project.
 *
 * Copyright (C) 2008 Stefan Wildemann <stefan.wildemann@kontron.com>
 * Copyright (C) 2008 Claus Gindhart <claus.gindhart@kontron.com>
 * Copyright (C) 2008 Dominik Geyer <dominik.geyer@kontron.com>
 * Copyright (C) 2008 coresystems GmbH <info@coresystems.de>
 * Copyright (C) 2009, 2010 Carl-Daniel Hailfinger
 *
 * 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.
 *
 * 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
 */

/*
 * This module is designed for supporting the devices
 * ST M25P40
 * ST M25P80
 * ST M25P16
 * ST M25P32 already tested
 * ST M25P64
 * AT 25DF321 already tested
 *
 */

#if defined(__i386__) || defined(__x86_64__)

#include <string.h>
#include "flash.h"
#include "chipdrivers.h"
#include "spi.h"

/* ICH9 controller register definition */
#define ICH9_REG_FADDR         0x08	/* 32 Bits */
#define ICH9_REG_FDATA0                0x10	/* 64 Bytes */

#define ICH9_REG_SSFS          0x90	/* 08 Bits */
#define SSFS_SCIP		0x00000001
#define SSFS_CDS		0x00000004
#define SSFS_FCERR		0x00000008
#define SSFS_AEL		0x00000010

#define ICH9_REG_SSFC          0x91	/* 24 Bits */
#define SSFC_SCGO		0x00000200
#define SSFC_ACS		0x00000400
#define SSFC_SPOP		0x00000800
#define SSFC_COP		0x00001000
#define SSFC_DBC		0x00010000
#define SSFC_DS			0x00400000
#define SSFC_SME		0x00800000
#define SSFC_SCF		0x01000000
#define SSFC_SCF_20MHZ 0x00000000
#define SSFC_SCF_33MHZ 0x01000000

#define ICH9_REG_PREOP         0x94	/* 16 Bits */
#define ICH9_REG_OPTYPE                0x96	/* 16 Bits */
#define ICH9_REG_OPMENU                0x98	/* 64 Bits */

// ICH9R SPI commands
#define SPI_OPCODE_TYPE_READ_NO_ADDRESS     0
#define SPI_OPCODE_TYPE_WRITE_NO_ADDRESS    1
#define SPI_OPCODE_TYPE_READ_WITH_ADDRESS   2
#define SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS  3

// ICH7 registers
#define ICH7_REG_SPIS          0x00	/* 16 Bits */
#define SPIS_SCIP              0x00000001
#define SPIS_CDS               0x00000004
#define SPIS_FCERR             0x00000008

/* VIA SPI is compatible with ICH7, but maxdata
   to transfer is 16 bytes.

   DATA byte count on ICH7 is 8:13, on VIA 8:11

   bit 12 is port select CS0 CS1
   bit 13 is FAST READ enable
   bit 7  is used with fast read and one shot controls CS de-assert?
*/

#define ICH7_REG_SPIC          0x02	/* 16 Bits */
#define SPIC_SCGO              0x0002
#define SPIC_ACS               0x0004
#define SPIC_SPOP              0x0008
#define SPIC_DS                0x4000

#define ICH7_REG_SPIA          0x04	/* 32 Bits */
#define ICH7_REG_SPID0         0x08	/* 64 Bytes */
#define ICH7_REG_PREOP         0x54	/* 16 Bits */
#define ICH7_REG_OPTYPE                0x56	/* 16 Bits */
#define ICH7_REG_OPMENU                0x58	/* 64 Bits */

/* ICH SPI configuration lock-down. May be set during chipset enabling. */
int ichspi_lock = 0;

uint32_t ichspi_bbar = 0;

void *ich_spibar = NULL;

typedef struct _OPCODE {
	uint8_t opcode;		//This commands spi opcode
	uint8_t spi_type;	//This commands spi type
	uint8_t atomic;		//Use preop: (0: none, 1: preop0, 2: preop1
} OPCODE;

/* Suggested opcode definition:
 * Preop 1: Write Enable
 * Preop 2: Write Status register enable
 *
 * OP 0: Write address
 * OP 1: Read Address
 * OP 2: ERASE block
 * OP 3: Read Status register
 * OP 4: Read ID
 * OP 5: Write Status register
 * OP 6: chip private (read JEDEC id)
 * OP 7: Chip erase
 */
typedef struct _OPCODES {
	uint8_t preop[2];
	OPCODE opcode[8];
} OPCODES;

static OPCODES *curopcodes = NULL;

/* HW access functions */
static uint32_t REGREAD32(int X)
{
	return mmio_readl(ich_spibar + X);
}

static uint16_t REGREAD16(int X)
{
	return mmio_readw(ich_spibar + X);
}

#define REGWRITE32(X,Y) mmio_writel(Y, ich_spibar+X)
#define REGWRITE16(X,Y) mmio_writew(Y, ich_spibar+X)
#define REGWRITE8(X,Y)  mmio_writeb(Y, ich_spibar+X)

/* Common SPI functions */
static int find_opcode(OPCODES *op, uint8_t opcode);
static int find_preop(OPCODES *op, uint8_t preop);
static int generate_opcodes(OPCODES * op);
static int program_opcodes(OPCODES * op);
static int run_opcode(OPCODE op, uint32_t offset,
		      uint8_t datalength, uint8_t * data);

/* for pairing opcodes with their required preop */
struct preop_opcode_pair {
	uint8_t preop;
	uint8_t opcode;
};

/* List of opcodes which need preopcodes and matching preopcodes. Unused. */
const struct preop_opcode_pair pops[] = {
	{JEDEC_WREN, JEDEC_BYTE_PROGRAM},
	{JEDEC_WREN, JEDEC_SE}, /* sector erase */
	{JEDEC_WREN, JEDEC_BE_52}, /* block erase */
	{JEDEC_WREN, JEDEC_BE_D8}, /* block erase */
	{JEDEC_WREN, JEDEC_CE_60}, /* chip erase */
	{JEDEC_WREN, JEDEC_CE_C7}, /* chip erase */
	 /* FIXME: WRSR requires either EWSR or WREN depending on chip type. */
	{JEDEC_WREN, JEDEC_WRSR},
	{JEDEC_EWSR, JEDEC_WRSR},
	{0,}
};

/* Reasonable default configuration. Needs ad-hoc modifications if we
 * encounter unlisted opcodes. Fun.
 */
static OPCODES O_ST_M25P = {
	{
	 JEDEC_WREN,
	 JEDEC_EWSR,
	},
	{
	 {JEDEC_BYTE_PROGRAM, SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS, 0},	// Write Byte
	 {JEDEC_READ, SPI_OPCODE_TYPE_READ_WITH_ADDRESS, 0},	// Read Data
	 {JEDEC_BE_D8, SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS, 0},	// Erase Sector
	 {JEDEC_RDSR, SPI_OPCODE_TYPE_READ_NO_ADDRESS, 0},	// Read Device Status Reg
	 {JEDEC_REMS, SPI_OPCODE_TYPE_READ_WITH_ADDRESS, 0},	// Read Electronic Manufacturer Signature
	 {JEDEC_WRSR, SPI_OPCODE_TYPE_WRITE_NO_ADDRESS, 0},	// Write Status Register
	 {JEDEC_RDID, SPI_OPCODE_TYPE_READ_NO_ADDRESS, 0},	// Read JDEC ID
	 {JEDEC_CE_C7, SPI_OPCODE_TYPE_WRITE_NO_ADDRESS, 0},	// Bulk erase
	}
};

static OPCODES O_EXISTING = {};

static int find_opcode(OPCODES *op, uint8_t opcode)
{
	int a;

	for (a = 0; a < 8; a++) {
		if (op->opcode[a].opcode == opcode)
			return a;
	}

	return -1;
}

static int find_preop(OPCODES *op, uint8_t preop)
{
	int a;

	for (a = 0; a < 2; a++) {
		if (op->preop[a] == preop)
			return a;
	}

	return -1;
}

/* Create a struct OPCODES based on what we find in the locked down chipset. */
static int generate_opcodes(OPCODES * op)
{
	int a;
	uint16_t preop, optype;
	uint32_t opmenu[2];

	if (op == NULL) {
		msg_perr("\n%s: null OPCODES pointer!\n", __func__);
		return -1;
	}

	switch (spi_controller) {
	case SPI_CONTROLLER_ICH7:
	case SPI_CONTROLLER_VIA:
		preop = REGREAD16(ICH7_REG_PREOP);
		optype = REGREAD16(ICH7_REG_OPTYPE);
		opmenu[0] = REGREAD32(ICH7_REG_OPMENU);
		opmenu[1] = REGREAD32(ICH7_REG_OPMENU + 4);
		break;
	case SPI_CONTROLLER_ICH9:
		preop = REGREAD16(ICH9_REG_PREOP);
		optype = REGREAD16(ICH9_REG_OPTYPE);
		opmenu[0] = REGREAD32(ICH9_REG_OPMENU);
		opmenu[1] = REGREAD32(ICH9_REG_OPMENU + 4);
		break;
	default:
		msg_perr("%s: unsupported chipset\n", __func__);
		return -1;
	}

	op->preop[0] = (uint8_t) preop;
	op->preop[1] = (uint8_t) (preop >> 8);

	for (a = 0; a < 8; a++) {
		op->opcode[a].spi_type = (uint8_t) (optype & 0x3);
		optype >>= 2;
	}

	for (a = 0; a < 4; a++) {
		op->opcode[a].opcode = (uint8_t) (opmenu[0] & 0xff);
		opmenu[0] >>= 8;
	}

	for (a = 4; a < 8; a++) {
		op->opcode[a].opcode = (uint8_t) (opmenu[1] & 0xff);
		opmenu[1] >>= 8;
	}

	/* No preopcodes used by default. */
	for (a = 0; a < 8; a++)
		op->opcode[a].atomic = 0;

	return 0;
}

int program_opcodes(OPCODES * op)
{
	uint8_t a;
	uint16_t preop, optype;
	uint32_t opmenu[2];

	/* Program Prefix Opcodes */
	/* 0:7 Prefix Opcode 1 */
	preop = (op->preop[0]);
	/* 8:16 Prefix Opcode 2 */
	preop |= ((uint16_t) op->preop[1]) << 8;

	/* Program Opcode Types 0 - 7 */
	optype = 0;
	for (a = 0; a < 8; a++) {
		optype |= ((uint16_t) op->opcode[a].spi_type) << (a * 2);
	}

	/* Program Allowable Opcodes 0 - 3 */
	opmenu[0] = 0;
	for (a = 0; a < 4; a++) {
		opmenu[0] |= ((uint32_t) op->opcode[a].opcode) << (a * 8);
	}

	/*Program Allowable Opcodes 4 - 7 */
	opmenu[1] = 0;
	for (a = 4; a < 8; a++) {
		opmenu[1] |= ((uint32_t) op->opcode[a].opcode) << ((a - 4) * 8);
	}

	msg_pdbg("\n%s: preop=%04x optype=%04x opmenu=%08x%08x\n", __func__, preop, optype, opmenu[0], opmenu[1]);
	switch (spi_controller) {
	case SPI_CONTROLLER_ICH7:
	case SPI_CONTROLLER_VIA:
		REGWRITE16(ICH7_REG_PREOP, preop);
		REGWRITE16(ICH7_REG_OPTYPE, optype);
		REGWRITE32(ICH7_REG_OPMENU, opmenu[0]);
		REGWRITE32(ICH7_REG_OPMENU + 4, opmenu[1]);
		break;
	case SPI_CONTROLLER_ICH9:
		REGWRITE16(ICH9_REG_PREOP, preop);
		REGWRITE16(ICH9_REG_OPTYPE, optype);
		REGWRITE32(ICH9_REG_OPMENU, opmenu[0]);
		REGWRITE32(ICH9_REG_OPMENU + 4, opmenu[1]);
		break;
	default:
		msg_perr("%s: unsupported chipset\n", __func__);
		return -1;
	}

	return 0;
}

/*
 * Try to set BBAR (BIOS Base Address Register), but read back the value in case
 * it didn't stick.
 */
void ich_set_bbar(uint32_t minaddr)
{
	switch (spi_controller) {
	case SPI_CONTROLLER_ICH7:
		mmio_writel(minaddr, ich_spibar + 0x50);
		ichspi_bbar = mmio_readl(ich_spibar + 0x50);
		/* We don't have any option except complaining. */
		if (ichspi_bbar != minaddr)
			msg_perr("Setting BBAR failed!\n");
		break;
	case SPI_CONTROLLER_ICH9:
		mmio_writel(minaddr, ich_spibar + 0xA0);
		ichspi_bbar = mmio_readl(ich_spibar + 0xA0);
		/* We don't have any option except complaining. */
		if (ichspi_bbar != minaddr)
			msg_perr("Setting BBAR failed!\n");
		break;
	default:
		/* Not sure if BBAR actually exists on VIA. */
		msg_pdbg("Setting BBAR is not implemented for VIA yet.\n");
		break;
	}
}

/* This function generates OPCODES from or programs OPCODES to ICH according to
 * the chipset's SPI configuration lock.
 *
 * It should be called before ICH sends any spi command.
 */
int ich_init_opcodes(void)
{
	int rc = 0;
	OPCODES *curopcodes_done;

	if (curopcodes)
		return 0;

	if (ichspi_lock) {
		msg_pdbg("Reading OPCODES... ");
		curopcodes_done = &O_EXISTING;
		rc = generate_opcodes(curopcodes_done);
	} else {
		msg_pdbg("Programming OPCODES... ");
		curopcodes_done = &O_ST_M25P;
		rc = program_opcodes(curopcodes_done);
		/* Technically not part of opcode init, but it allows opcodes
		 * to run without transaction errors by setting the lowest
		 * allowed address to zero.
		 */
		ich_set_bbar(0);
	}

	if (rc) {
		curopcodes = NULL;
		msg_perr("failed\n");
		return 1;
	} else {
		curopcodes = curopcodes_done;
		msg_pdbg("done\n");
		return 0;
	}
}

static int ich7_run_opcode(OPCODE op, uint32_t offset,
			   uint8_t datalength, uint8_t * data, int maxdata)
{
	int write_cmd = 0;
	int timeout;
	uint32_t temp32 = 0;
	uint16_t temp16;
	uint32_t a;
	uint64_t opmenu;
	int opcode_index;

	/* Is it a write command? */
	if ((op.spi_type == SPI_OPCODE_TYPE_WRITE_NO_ADDRESS)
	    || (op.spi_type == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS)) {
		write_cmd = 1;
	}

	/* Programm Offset in Flash into FADDR */
	REGWRITE32(ICH7_REG_SPIA, (offset & 0x00FFFFFF));	/* SPI addresses are 24 BIT only */

	/* Program data into FDATA0 to N */
	if (write_cmd && (datalength != 0)) {
		temp32 = 0;
		for (a = 0; a < datalength; a++) {
			if ((a % 4) == 0) {
				temp32 = 0;
			}

			temp32 |= ((uint32_t) data[a]) << ((a % 4) * 8);

			if ((a % 4) == 3) {
				REGWRITE32(ICH7_REG_SPID0 + (a - (a % 4)),
					   temp32);
			}
		}
		if (((a - 1) % 4) != 3) {
			REGWRITE32(ICH7_REG_SPID0 +
				   ((a - 1) - ((a - 1) % 4)), temp32);
		}

	}

	/* Assemble SPIS */
	temp16 = 0;
	/* clear error status registers */
	temp16 |= (SPIS_CDS + SPIS_FCERR);
	REGWRITE16(ICH7_REG_SPIS, temp16);

	/* Assemble SPIC */
	temp16 = 0;

	if (datalength != 0) {
		temp16 |= SPIC_DS;
		temp16 |= ((uint32_t) ((datalength - 1) & (maxdata - 1))) << 8;
	}

	/* Select opcode */
	opmenu = REGREAD32(ICH7_REG_OPMENU);
	opmenu |= ((uint64_t)REGREAD32(ICH7_REG_OPMENU + 4)) << 32;

	for (opcode_index = 0; opcode_index < 8; opcode_index++) {
		if ((opmenu & 0xff) == op.opcode) {
			break;
		}
		opmenu >>= 8;
	}
	if (opcode_index == 8) {
		msg_pdbg("Opcode %x not found.\n", op.opcode);
		return 1;
	}
	temp16 |= ((uint16_t) (opcode_index & 0x07)) << 4;

	/* Handle Atomic */
	switch (op.atomic) {
	case 2:
		/* Select second preop. */
		temp16 |= SPIC_SPOP;
		/* And fall through. */
	case 1:
		/* Atomic command (preop+op) */
		temp16 |= SPIC_ACS;
		break;
	}

	/* Start */
	temp16 |= SPIC_SCGO;

	/* write it */
	REGWRITE16(ICH7_REG_SPIC, temp16);

	/* wait for cycle complete */
	timeout = 100 * 1000 * 60;	// 60s is a looong timeout.
	while (((REGREAD16(ICH7_REG_SPIS) & SPIS_CDS) == 0) && --timeout) {
		programmer_delay(10);
	}
	if (!timeout) {
		msg_perr("timeout\n");
	}

	/* FIXME: make sure we do not needlessly cause transaction errors. */
	if ((REGREAD16(ICH7_REG_SPIS) & SPIS_FCERR) != 0) {
		msg_pdbg("Transaction error!\n");
		return 1;
	}

	if ((!write_cmd) && (datalength != 0)) {
		for (a = 0; a < datalength; a++) {
			if ((a % 4) == 0) {
				temp32 = REGREAD32(ICH7_REG_SPID0 + (a));
			}

			data[a] =
			    (temp32 & (((uint32_t) 0xff) << ((a % 4) * 8)))
			    >> ((a % 4) * 8);
		}
	}

	return 0;
}

static int ich9_run_opcode(OPCODE op, uint32_t offset,
			   uint8_t datalength, uint8_t * data)
{
	int write_cmd = 0;
	int timeout;
	uint32_t temp32;
	uint32_t a;
	uint64_t opmenu;
	int opcode_index;

	/* Is it a write command? */
	if ((op.spi_type == SPI_OPCODE_TYPE_WRITE_NO_ADDRESS)
	    || (op.spi_type == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS)) {
		write_cmd = 1;
	}

	/* Programm Offset in Flash into FADDR */
	REGWRITE32(ICH9_REG_FADDR, (offset & 0x00FFFFFF));	/* SPI addresses are 24 BIT only */

	/* Program data into FDATA0 to N */
	if (write_cmd && (datalength != 0)) {
		temp32 = 0;
		for (a = 0; a < datalength; a++) {
			if ((a % 4) == 0) {
				temp32 = 0;
			}

			temp32 |= ((uint32_t) data[a]) << ((a % 4) * 8);

			if ((a % 4) == 3) {
				REGWRITE32(ICH9_REG_FDATA0 + (a - (a % 4)),
					   temp32);
			}
		}
		if (((a - 1) % 4) != 3) {
			REGWRITE32(ICH9_REG_FDATA0 +
				   ((a - 1) - ((a - 1) % 4)), temp32);
		}
	}

	/* Assemble SSFS + SSFC */
	temp32 = 0;

	/* clear error status registers */
	temp32 |= (SSFS_CDS + SSFS_FCERR);
	/* Use 20 MHz */
	temp32 |= SSFC_SCF_20MHZ;

	if (datalength != 0) {
		uint32_t datatemp;
		temp32 |= SSFC_DS;
		datatemp = ((uint32_t) ((datalength - 1) & 0x3f)) << (8 + 8);
		temp32 |= datatemp;
	}

	/* Select opcode */
	opmenu = REGREAD32(ICH9_REG_OPMENU);
	opmenu |= ((uint64_t)REGREAD32(ICH9_REG_OPMENU + 4)) << 32;

	for (opcode_index = 0; opcode_index < 8; opcode_index++) {
		if ((opmenu & 0xff) == op.opcode) {
			break;
		}
		opmenu >>= 8;
	}
	if (opcode_index == 8) {
		msg_pdbg("Opcode %x not found.\n", op.opcode);
		return 1;
	}
	temp32 |= ((uint32_t) (opcode_index & 0x07)) << (8 + 4);

	/* Handle Atomic */
	switch (op.atomic) {
	case 2:
		/* Select second preop. */
		temp32 |= SSFC_SPOP;
		/* And fall through. */
	case 1:
		/* Atomic command (preop+op) */
		temp32 |= SSFC_ACS;
		break;
	}

	/* Start */
	temp32 |= SSFC_SCGO;

	/* write it */
	REGWRITE32(ICH9_REG_SSFS, temp32);

	/*wait for cycle complete */
	timeout = 100 * 1000 * 60;	// 60s is a looong timeout.
	while (((REGREAD32(ICH9_REG_SSFS) & SSFS_CDS) == 0) && --timeout) {
		programmer_delay(10);
	}
	if (!timeout) {
		msg_perr("timeout\n");
	}

	/* FIXME make sure we do not needlessly cause transaction errors. */
	if ((REGREAD32(ICH9_REG_SSFS) & SSFS_FCERR) != 0) {
		msg_pdbg("Transaction error!\n");
		return 1;
	}

	if ((!write_cmd) && (datalength != 0)) {
		for (a = 0; a < datalength; a++) {
			if ((a % 4) == 0) {
				temp32 = REGREAD32(ICH9_REG_FDATA0 + (a));
			}

			data[a] =
			    (temp32 & (((uint32_t) 0xff) << ((a % 4) * 8)))
			    >> ((a % 4) * 8);
		}
	}

	return 0;
}

static int run_opcode(OPCODE op, uint32_t offset,
		      uint8_t datalength, uint8_t * data)
{
	switch (spi_controller) {
	case SPI_CONTROLLER_VIA:
		if (datalength > 16) {
			msg_perr("%s: Internal command size error for "
				"opcode 0x%02x, got datalength=%i, want <=16\n",
				__func__, op.opcode, datalength);
			return SPI_INVALID_LENGTH;
		}
		return ich7_run_opcode(op, offset, datalength, data, 16);
	case SPI_CONTROLLER_ICH7:
		if (datalength > 64) {
			msg_perr("%s: Internal command size error for "
				"opcode 0x%02x, got datalength=%i, want <=16\n",
				__func__, op.opcode, datalength);
			return SPI_INVALID_LENGTH;
		}
		return ich7_run_opcode(op, offset, datalength, data, 64);
	case SPI_CONTROLLER_ICH9:
		if (datalength > 64) {
			msg_perr("%s: Internal command size error for "
				"opcode 0x%02x, got datalength=%i, want <=16\n",
				__func__, op.opcode, datalength);
			return SPI_INVALID_LENGTH;
		}
		return ich9_run_opcode(op, offset, datalength, data);
	default:
		msg_perr("%s: unsupported chipset\n", __func__);
	}

	/* If we ever get here, something really weird happened */
	return -1;
}

int ich_spi_read(struct flashchip *flash, uint8_t * buf, int start, int len)
{
	int maxdata = 64;

	if (spi_controller == SPI_CONTROLLER_VIA)
		maxdata = 16;

	return spi_read_chunked(flash, buf, start, len, maxdata);
}

int ich_spi_write_256(struct flashchip *flash, uint8_t * buf, int start, int len)
{
	int maxdata = 64;

	if (spi_controller == SPI_CONTROLLER_VIA)
		maxdata = 16;

	spi_disable_blockprotect();
	return spi_write_chunked(flash, buf, start, len, maxdata);
}

int ich_spi_send_command(unsigned int writecnt, unsigned int readcnt,
		    const unsigned char *writearr, unsigned char *readarr)
{
	int result;
	int opcode_index = -1;
	const unsigned char cmd = *writearr;
	OPCODE *opcode;
	uint32_t addr = 0;
	uint8_t *data;
	int count;

	/* find cmd in opcodes-table */
	opcode_index = find_opcode(curopcodes, cmd);
	if (opcode_index == -1) {
		/* FIXME: Reprogram opcodes if possible. Autodetect type of
		 * opcode by checking readcnt/writecnt.
		 */
		msg_pdbg("Invalid OPCODE 0x%02x\n", cmd);
		return SPI_INVALID_OPCODE;
	}

	opcode = &(curopcodes->opcode[opcode_index]);

	/* The following valid writecnt/readcnt combinations exist:
	 * writecnt  = 4, readcnt >= 0
	 * writecnt  = 1, readcnt >= 0
	 * writecnt >= 4, readcnt  = 0
	 * writecnt >= 1, readcnt  = 0
	 * writecnt >= 1 is guaranteed for all commands.
	 */
	if ((opcode->spi_type == SPI_OPCODE_TYPE_READ_WITH_ADDRESS) &&
	    (writecnt != 4)) {
		msg_perr("%s: Internal command size error for opcode "
			"0x%02x, got writecnt=%i, want =4\n", __func__, cmd,
			writecnt);
		return SPI_INVALID_LENGTH;
	}
	if ((opcode->spi_type == SPI_OPCODE_TYPE_READ_NO_ADDRESS) &&
	    (writecnt != 1)) {
		msg_perr("%s: Internal command size error for opcode "
			"0x%02x, got writecnt=%i, want =1\n", __func__, cmd,
			writecnt);
		return SPI_INVALID_LENGTH;
	}
	if ((opcode->spi_type == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS) &&
	    (writecnt < 4)) {
		msg_perr("%s: Internal command size error for opcode "
			"0x%02x, got writecnt=%i, want >=4\n", __func__, cmd,
			writecnt);
		return SPI_INVALID_LENGTH;
	}
	if (((opcode->spi_type == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS) ||
	     (opcode->spi_type == SPI_OPCODE_TYPE_WRITE_NO_ADDRESS)) &&
	    (readcnt)) {
		msg_perr("%s: Internal command size error for opcode "
			"0x%02x, got readcnt=%i, want =0\n", __func__, cmd,
			readcnt);
		return SPI_INVALID_LENGTH;
	}

	/* if opcode-type requires an address */
	if (opcode->spi_type == SPI_OPCODE_TYPE_READ_WITH_ADDRESS ||
	    opcode->spi_type == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS) {
		addr = (writearr[1] << 16) |
		    (writearr[2] << 8) | (writearr[3] << 0);
		switch (spi_controller) {
		case SPI_CONTROLLER_ICH7:
		case SPI_CONTROLLER_ICH9:
			if (addr < ichspi_bbar) {
				msg_perr("%s: Address 0x%06x below allowed "
					 "range 0x%06x-0xffffff\n", __func__,
					 addr, ichspi_bbar);
				return SPI_INVALID_ADDRESS;
			}
			break;
		default:
			break;
		}
	}

	/* translate read/write array/count */
	if (opcode->spi_type == SPI_OPCODE_TYPE_WRITE_NO_ADDRESS) {
		data = (uint8_t *) (writearr + 1);
		count = writecnt - 1;
	} else if (opcode->spi_type == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS) {
		data = (uint8_t *) (writearr + 4);
		count = writecnt - 4;
	} else {
		data = (uint8_t *) readarr;
		count = readcnt;
	}

	result = run_opcode(*opcode, addr, count, data);
	if (result) {
		msg_pdbg("run OPCODE 0x%02x failed\n", opcode->opcode);
	}

	return result;
}

int ich_spi_send_multicommand(struct spi_command *cmds)
{
	int ret = 0;
	int i;
	int oppos, preoppos;
	for (; (cmds->writecnt || cmds->readcnt) && !ret; cmds++) {
		if ((cmds + 1)->writecnt || (cmds + 1)->readcnt) {
			/* Next command is valid. */
			preoppos = find_preop(curopcodes, cmds->writearr[0]);
			oppos = find_opcode(curopcodes, (cmds + 1)->writearr[0]);
			if ((oppos == -1) && (preoppos != -1)) {
				/* Current command is listed as preopcode in
				 * ICH struct OPCODES, but next command is not
				 * listed as opcode in that struct.
				 * Check for command sanity, then
				 * try to reprogram the ICH opcode list.
				 */
				if (find_preop(curopcodes,
					       (cmds + 1)->writearr[0]) != -1) {
					msg_perr("%s: Two subsequent "
						"preopcodes 0x%02x and 0x%02x, "
						"ignoring the first.\n",
						__func__, cmds->writearr[0],
						(cmds + 1)->writearr[0]);
					continue;
				}
				/* If the chipset is locked down, we'll fail
				 * during execution of the next command anyway.
				 * No need to bother with fixups.
				 */
				if (!ichspi_lock) {
					msg_pdbg("%s: FIXME: Add on-the-fly"
						     " reprogramming of the "
						     "chipset opcode list.\n",
						     __func__);
				 	/* FIXME: Reprogram opcode menu.
					 * Find a less-useful opcode, replace it
					 * with the wanted opcode, detect optype
					 * and reprogram the opcode menu.
					 * Update oppos so the next if-statement
					 * can do something useful.
					 */
					//curopcodes.opcode[lessusefulindex] = (cmds + 1)->writearr[0]);
					//update_optypes(curopcodes);
					//program_opcodes(curopcodes);
					//oppos = find_opcode(curopcodes, (cmds + 1)->writearr[0]);
					continue;
				}
			}
			if ((oppos != -1) && (preoppos != -1)) {
				/* Current command is listed as preopcode in
				 * ICH struct OPCODES and next command is listed
				 * as opcode in that struct. Match them up.
				 */
				curopcodes->opcode[oppos].atomic = preoppos + 1;
				continue;
			}
			/* If none of the above if-statements about oppos or
			 * preoppos matched, this is a normal opcode.
			 */
		}
		ret = ich_spi_send_command(cmds->writecnt, cmds->readcnt,
					   cmds->writearr, cmds->readarr);
		/* Reset the type of all opcodes to non-atomic. */
		for (i = 0; i < 8; i++)
			curopcodes->opcode[i].atomic = 0;
	}
	return ret;
}

#endif