iMX28: Add image header generator tool

This tool can now generate proper image for "BootStream" files.

NOTE: This tool now works only for NAND.

Signed-off-by: Marek Vasut <marek.vasut@gmail.com>
Cc: Stefano Babic <sbabic@denx.de>
Cc: Wolfgang Denk <wd@denx.de>
Cc: Detlev Zundel <dzu@denx.de>
diff --git a/tools/mxsboot.c b/tools/mxsboot.c
new file mode 100644
index 0000000..176753d
--- /dev/null
+++ b/tools/mxsboot.c
@@ -0,0 +1,684 @@
+/*
+ * Freescale i.MX28 image generator
+ *
+ * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
+ * on behalf of DENX Software Engineering GmbH
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "compiler.h"
+
+/*
+ * Default BCB layout.
+ *
+ * TWEAK this if you have blown any OCOTP fuses.
+ */
+#define	STRIDE_PAGES		64
+#define	STRIDE_COUNT		4
+
+/*
+ * Layout for 256Mb big NAND with 2048b page size, 64b OOB size and
+ * 128kb erase size.
+ *
+ * TWEAK this if you have different kind of NAND chip.
+ */
+uint32_t nand_writesize = 2048;
+uint32_t nand_oobsize = 64;
+uint32_t nand_erasesize = 128 * 1024;
+
+/*
+ * Sector on which the SigmaTel boot partition (0x53) starts.
+ */
+uint32_t sd_sector = 2048;
+
+/*
+ * Each of the U-Boot bootstreams is at maximum 1MB big.
+ *
+ * TWEAK this if, for some wild reason, you need to boot bigger image.
+ */
+#define	MAX_BOOTSTREAM_SIZE	(1 * 1024 * 1024)
+
+/* i.MX28 NAND controller-specific constants. DO NOT TWEAK! */
+#define	MXS_NAND_DMA_DESCRIPTOR_COUNT		4
+#define	MXS_NAND_CHUNK_DATA_CHUNK_SIZE		512
+#define	MXS_NAND_METADATA_SIZE			10
+#define	MXS_NAND_COMMAND_BUFFER_SIZE		32
+
+struct mx28_nand_fcb {
+	uint32_t		checksum;
+	uint32_t		fingerprint;
+	uint32_t		version;
+	struct {
+		uint8_t			data_setup;
+		uint8_t			data_hold;
+		uint8_t			address_setup;
+		uint8_t			dsample_time;
+		uint8_t			nand_timing_state;
+		uint8_t			rea;
+		uint8_t			rloh;
+		uint8_t			rhoh;
+	}			timing;
+	uint32_t		page_data_size;
+	uint32_t		total_page_size;
+	uint32_t		sectors_per_block;
+	uint32_t		number_of_nands;		/* Ignored */
+	uint32_t		total_internal_die;		/* Ignored */
+	uint32_t		cell_type;			/* Ignored */
+	uint32_t		ecc_block_n_ecc_type;
+	uint32_t		ecc_block_0_size;
+	uint32_t		ecc_block_n_size;
+	uint32_t		ecc_block_0_ecc_type;
+	uint32_t		metadata_bytes;
+	uint32_t		num_ecc_blocks_per_page;
+	uint32_t		ecc_block_n_ecc_level_sdk;	/* Ignored */
+	uint32_t		ecc_block_0_size_sdk;		/* Ignored */
+	uint32_t		ecc_block_n_size_sdk;		/* Ignored */
+	uint32_t		ecc_block_0_ecc_level_sdk;	/* Ignored */
+	uint32_t		num_ecc_blocks_per_page_sdk;	/* Ignored */
+	uint32_t		metadata_bytes_sdk;		/* Ignored */
+	uint32_t		erase_threshold;
+	uint32_t		boot_patch;
+	uint32_t		patch_sectors;
+	uint32_t		firmware1_starting_sector;
+	uint32_t		firmware2_starting_sector;
+	uint32_t		sectors_in_firmware1;
+	uint32_t		sectors_in_firmware2;
+	uint32_t		dbbt_search_area_start_address;
+	uint32_t		badblock_marker_byte;
+	uint32_t		badblock_marker_start_bit;
+	uint32_t		bb_marker_physical_offset;
+};
+
+struct mx28_nand_dbbt {
+	uint32_t		checksum;
+	uint32_t		fingerprint;
+	uint32_t		version;
+	uint32_t		number_bb;
+	uint32_t		number_2k_pages_bb;
+};
+
+struct mx28_nand_bbt {
+	uint32_t		nand;
+	uint32_t		number_bb;
+	uint32_t		badblock[510];
+};
+
+struct mx28_sd_drive_info {
+	uint32_t		chip_num;
+	uint32_t		drive_type;
+	uint32_t		tag;
+	uint32_t		first_sector_number;
+	uint32_t		sector_count;
+};
+
+struct mx28_sd_config_block {
+	uint32_t			signature;
+	uint32_t			primary_boot_tag;
+	uint32_t			secondary_boot_tag;
+	uint32_t			num_copies;
+	struct mx28_sd_drive_info	drv_info[1];
+};
+
+static inline uint32_t mx28_nand_ecc_size_in_bits(uint32_t ecc_strength)
+{
+	return ecc_strength * 13;
+}
+
+static inline uint32_t mx28_nand_get_ecc_strength(uint32_t page_data_size,
+						uint32_t page_oob_size)
+{
+	if (page_data_size == 2048)
+		return 8;
+
+	if (page_data_size == 4096) {
+		if (page_oob_size == 128)
+			return 8;
+
+		if (page_oob_size == 218)
+			return 16;
+	}
+
+	return 0;
+}
+
+static inline uint32_t mx28_nand_get_mark_offset(uint32_t page_data_size,
+						uint32_t ecc_strength)
+{
+	uint32_t chunk_data_size_in_bits;
+	uint32_t chunk_ecc_size_in_bits;
+	uint32_t chunk_total_size_in_bits;
+	uint32_t block_mark_chunk_number;
+	uint32_t block_mark_chunk_bit_offset;
+	uint32_t block_mark_bit_offset;
+
+	chunk_data_size_in_bits = MXS_NAND_CHUNK_DATA_CHUNK_SIZE * 8;
+	chunk_ecc_size_in_bits  = mx28_nand_ecc_size_in_bits(ecc_strength);
+
+	chunk_total_size_in_bits =
+			chunk_data_size_in_bits + chunk_ecc_size_in_bits;
+
+	/* Compute the bit offset of the block mark within the physical page. */
+	block_mark_bit_offset = page_data_size * 8;
+
+	/* Subtract the metadata bits. */
+	block_mark_bit_offset -= MXS_NAND_METADATA_SIZE * 8;
+
+	/*
+	 * Compute the chunk number (starting at zero) in which the block mark
+	 * appears.
+	 */
+	block_mark_chunk_number =
+			block_mark_bit_offset / chunk_total_size_in_bits;
+
+	/*
+	 * Compute the bit offset of the block mark within its chunk, and
+	 * validate it.
+	 */
+	block_mark_chunk_bit_offset = block_mark_bit_offset -
+			(block_mark_chunk_number * chunk_total_size_in_bits);
+
+	if (block_mark_chunk_bit_offset > chunk_data_size_in_bits)
+		return 1;
+
+	/*
+	 * Now that we know the chunk number in which the block mark appears,
+	 * we can subtract all the ECC bits that appear before it.
+	 */
+	block_mark_bit_offset -=
+		block_mark_chunk_number * chunk_ecc_size_in_bits;
+
+	return block_mark_bit_offset;
+}
+
+static inline uint32_t mx28_nand_mark_byte_offset(void)
+{
+	uint32_t ecc_strength;
+	ecc_strength = mx28_nand_get_ecc_strength(nand_writesize, nand_oobsize);
+	return mx28_nand_get_mark_offset(nand_writesize, ecc_strength) >> 3;
+}
+
+static inline uint32_t mx28_nand_mark_bit_offset(void)
+{
+	uint32_t ecc_strength;
+	ecc_strength = mx28_nand_get_ecc_strength(nand_writesize, nand_oobsize);
+	return mx28_nand_get_mark_offset(nand_writesize, ecc_strength) & 0x7;
+}
+
+static uint32_t mx28_nand_block_csum(uint8_t *block, uint32_t size)
+{
+	uint32_t csum = 0;
+	int i;
+
+	for (i = 0; i < size; i++)
+		csum += block[i];
+
+	return csum ^ 0xffffffff;
+}
+
+static struct mx28_nand_fcb *mx28_nand_get_fcb(uint32_t size)
+{
+	struct mx28_nand_fcb *fcb;
+	uint32_t bcb_size_bytes;
+	uint32_t stride_size_bytes;
+	uint32_t bootstream_size_pages;
+	uint32_t fw1_start_page;
+	uint32_t fw2_start_page;
+
+	fcb = malloc(nand_writesize);
+	if (!fcb) {
+		printf("MX28 NAND: Unable to allocate FCB\n");
+		return NULL;
+	}
+
+	memset(fcb, 0, nand_writesize);
+
+	fcb->fingerprint =			0x20424346;
+	fcb->version =				0x01000000;
+
+	/*
+	 * FIXME: These here are default values as found in kobs-ng. We should
+	 * probably retrieve the data from NAND or something.
+	 */
+	fcb->timing.data_setup =		80;
+	fcb->timing.data_hold =			60;
+	fcb->timing.address_setup =		25;
+	fcb->timing.dsample_time =		6;
+
+	fcb->page_data_size =		nand_writesize;
+	fcb->total_page_size =		nand_writesize + nand_oobsize;
+	fcb->sectors_per_block =	nand_erasesize / nand_writesize;
+
+	fcb->num_ecc_blocks_per_page =	(nand_writesize / 512) - 1;
+	fcb->ecc_block_0_size =		512;
+	fcb->ecc_block_n_size =		512;
+	fcb->metadata_bytes =		10;
+
+	if (nand_writesize == 2048) {
+		fcb->ecc_block_n_ecc_type =		4;
+		fcb->ecc_block_0_ecc_type =		4;
+	} else if (nand_writesize == 4096) {
+		if (nand_oobsize == 128) {
+			fcb->ecc_block_n_ecc_type =	4;
+			fcb->ecc_block_0_ecc_type =	4;
+		} else if (nand_oobsize == 218) {
+			fcb->ecc_block_n_ecc_type =	8;
+			fcb->ecc_block_0_ecc_type =	8;
+		}
+	}
+
+	if (fcb->ecc_block_n_ecc_type == 0) {
+		printf("MX28 NAND: Unsupported NAND geometry\n");
+		goto err;
+	}
+
+	fcb->boot_patch =			0;
+	fcb->patch_sectors =			0;
+
+	fcb->badblock_marker_byte =	mx28_nand_mark_byte_offset();
+	fcb->badblock_marker_start_bit = mx28_nand_mark_bit_offset();
+	fcb->bb_marker_physical_offset = nand_writesize;
+
+	stride_size_bytes = STRIDE_PAGES * nand_writesize;
+	bcb_size_bytes = stride_size_bytes * STRIDE_COUNT;
+
+	bootstream_size_pages = (size + (nand_writesize - 1)) /
+					nand_writesize;
+
+	fw1_start_page = 2 * bcb_size_bytes / nand_writesize;
+	fw2_start_page = (2 * bcb_size_bytes + MAX_BOOTSTREAM_SIZE) /
+				nand_writesize;
+
+	fcb->firmware1_starting_sector =	fw1_start_page;
+	fcb->firmware2_starting_sector =	fw2_start_page;
+	fcb->sectors_in_firmware1 =		bootstream_size_pages;
+	fcb->sectors_in_firmware2 =		bootstream_size_pages;
+
+	fcb->dbbt_search_area_start_address =	STRIDE_PAGES * STRIDE_COUNT;
+
+	return fcb;
+
+err:
+	free(fcb);
+	return NULL;
+}
+
+static struct mx28_nand_dbbt *mx28_nand_get_dbbt(void)
+{
+	struct mx28_nand_dbbt *dbbt;
+
+	dbbt = malloc(nand_writesize);
+	if (!dbbt) {
+		printf("MX28 NAND: Unable to allocate DBBT\n");
+		return NULL;
+	}
+
+	memset(dbbt, 0, nand_writesize);
+
+	dbbt->fingerprint	= 0x54424244;
+	dbbt->version		= 0x1;
+
+	return dbbt;
+}
+
+static inline uint8_t mx28_nand_parity_13_8(const uint8_t b)
+{
+	uint32_t parity = 0, tmp;
+
+	tmp = ((b >> 6) ^ (b >> 5) ^ (b >> 3) ^ (b >> 2)) & 1;
+	parity |= tmp << 0;
+
+	tmp = ((b >> 7) ^ (b >> 5) ^ (b >> 4) ^ (b >> 2) ^ (b >> 1)) & 1;
+	parity |= tmp << 1;
+
+	tmp = ((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 1) ^ (b >> 0)) & 1;
+	parity |= tmp << 2;
+
+	tmp = ((b >> 7) ^ (b >> 4) ^ (b >> 3) ^ (b >> 0)) & 1;
+	parity |= tmp << 3;
+
+	tmp = ((b >> 6) ^ (b >> 4) ^ (b >> 3) ^
+		(b >> 2) ^ (b >> 1) ^ (b >> 0)) & 1;
+	parity |= tmp << 4;
+
+	return parity;
+}
+
+static uint8_t *mx28_nand_fcb_block(struct mx28_nand_fcb *fcb)
+{
+	uint8_t *block;
+	uint8_t *ecc;
+	int i;
+
+	block = malloc(nand_writesize + nand_oobsize);
+	if (!block) {
+		printf("MX28 NAND: Unable to allocate FCB block\n");
+		return NULL;
+	}
+
+	memset(block, 0, nand_writesize + nand_oobsize);
+
+	/* Update the FCB checksum */
+	fcb->checksum = mx28_nand_block_csum(((uint8_t *)fcb) + 4, 508);
+
+	/* Figure 12-11. in iMX28RM, rev. 1, says FCB is at offset 12 */
+	memcpy(block + 12, fcb, sizeof(struct mx28_nand_fcb));
+
+	/* ECC is at offset 12 + 512 */
+	ecc = block + 12 + 512;
+
+	/* Compute the ECC parity */
+	for (i = 0; i < sizeof(struct mx28_nand_fcb); i++)
+		ecc[i] = mx28_nand_parity_13_8(block[i + 12]);
+
+	return block;
+}
+
+static int mx28_nand_write_fcb(struct mx28_nand_fcb *fcb, char *buf)
+{
+	uint32_t offset;
+	uint8_t *fcbblock;
+	int ret = 0;
+	int i;
+
+	fcbblock = mx28_nand_fcb_block(fcb);
+	if (!fcbblock)
+		return -1;
+
+	for (i = 0; i < STRIDE_PAGES * STRIDE_COUNT; i += STRIDE_PAGES) {
+		offset = i * nand_writesize;
+		memcpy(buf + offset, fcbblock, nand_writesize + nand_oobsize);
+	}
+
+	free(fcbblock);
+	return ret;
+}
+
+static int mx28_nand_write_dbbt(struct mx28_nand_dbbt *dbbt, char *buf)
+{
+	uint32_t offset;
+	int i = STRIDE_PAGES * STRIDE_COUNT;
+
+	for (; i < 2 * STRIDE_PAGES * STRIDE_COUNT; i += STRIDE_PAGES) {
+		offset = i * nand_writesize;
+		memcpy(buf + offset, dbbt, sizeof(struct mx28_nand_dbbt));
+	}
+
+	return 0;
+}
+
+static int mx28_nand_write_firmware(struct mx28_nand_fcb *fcb, int infd,
+					char *buf)
+{
+	int ret;
+	off_t size;
+	uint32_t offset1, offset2;
+
+	size = lseek(infd, 0, SEEK_END);
+	lseek(infd, 0, SEEK_SET);
+
+	offset1 = fcb->firmware1_starting_sector * nand_writesize;
+	offset2 = fcb->firmware2_starting_sector * nand_writesize;
+
+	ret = read(infd, buf + offset1, size);
+	if (ret != size)
+		return -1;
+
+	memcpy(buf + offset2, buf + offset1, size);
+
+	return 0;
+}
+
+void usage(void)
+{
+	printf(
+		"Usage: mx28image [ops] <type> <infile> <outfile>\n"
+		"Augment BootStream file with a proper header for i.MX28 boot\n"
+		"\n"
+		"  <type>	type of image:\n"
+		"                 \"nand\" for NAND image\n"
+		"                 \"sd\" for SD image\n"
+		"  <infile>     input file, the u-boot.sb bootstream\n"
+		"  <outfile>    output file, the bootable image\n"
+		"\n");
+	printf(
+		"For NAND boot, these options are accepted:\n"
+		"  -w <size>    NAND page size\n"
+		"  -o <size>    NAND OOB size\n"
+		"  -e <size>    NAND erase size\n"
+		"\n"
+		"For SD boot, these options are accepted:\n"
+		"  -p <sector>  Sector where the SGTL partition starts\n"
+	);
+}
+
+static int mx28_create_nand_image(int infd, int outfd)
+{
+	struct mx28_nand_fcb *fcb;
+	struct mx28_nand_dbbt *dbbt;
+	int ret = -1;
+	char *buf;
+	int size;
+	ssize_t wr_size;
+
+	size = nand_writesize * 512 + 2 * MAX_BOOTSTREAM_SIZE;
+
+	buf = malloc(size);
+	if (!buf) {
+		printf("Can not allocate output buffer of %d bytes\n", size);
+		goto err0;
+	}
+
+	memset(buf, 0, size);
+
+	fcb = mx28_nand_get_fcb(MAX_BOOTSTREAM_SIZE);
+	if (!fcb) {
+		printf("Unable to compile FCB\n");
+		goto err1;
+	}
+
+	dbbt = mx28_nand_get_dbbt();
+	if (!dbbt) {
+		printf("Unable to compile DBBT\n");
+		goto err2;
+	}
+
+	ret = mx28_nand_write_fcb(fcb, buf);
+	if (ret) {
+		printf("Unable to write FCB to buffer\n");
+		goto err3;
+	}
+
+	ret = mx28_nand_write_dbbt(dbbt, buf);
+	if (ret) {
+		printf("Unable to write DBBT to buffer\n");
+		goto err3;
+	}
+
+	ret = mx28_nand_write_firmware(fcb, infd, buf);
+	if (ret) {
+		printf("Unable to write firmware to buffer\n");
+		goto err3;
+	}
+
+	wr_size = write(outfd, buf, size);
+	if (wr_size != size) {
+		ret = -1;
+		goto err3;
+	}
+
+	ret = 0;
+
+err3:
+	free(dbbt);
+err2:
+	free(fcb);
+err1:
+	free(buf);
+err0:
+	return ret;
+}
+
+static int mx28_create_sd_image(int infd, int outfd)
+{
+	int ret = -1;
+	uint32_t *buf;
+	int size;
+	off_t fsize;
+	ssize_t wr_size;
+	struct mx28_sd_config_block *cb;
+
+	fsize = lseek(infd, 0, SEEK_END);
+	lseek(infd, 0, SEEK_SET);
+	size = fsize + 512;
+
+	buf = malloc(size);
+	if (!buf) {
+		printf("Can not allocate output buffer of %d bytes\n", size);
+		goto err0;
+	}
+
+	ret = read(infd, (uint8_t *)buf + 512, fsize);
+	if (ret != fsize) {
+		ret = -1;
+		goto err1;
+	}
+
+	cb = (struct mx28_sd_config_block *)buf;
+
+	cb->signature = 0x00112233;
+	cb->primary_boot_tag = 0x1;
+	cb->secondary_boot_tag = 0x1;
+	cb->num_copies = 1;
+	cb->drv_info[0].chip_num = 0x0;
+	cb->drv_info[0].drive_type = 0x0;
+	cb->drv_info[0].tag = 0x1;
+	cb->drv_info[0].first_sector_number = sd_sector + 1;
+	cb->drv_info[0].sector_count = (size - 1) / 512;
+
+	wr_size = write(outfd, buf, size);
+	if (wr_size != size) {
+		ret = -1;
+		goto err1;
+	}
+
+	ret = 0;
+
+err1:
+	free(buf);
+err0:
+	return ret;
+}
+
+int parse_ops(int argc, char **argv)
+{
+	int i;
+	int tmp;
+	char *end;
+	enum param {
+		PARAM_WRITE,
+		PARAM_OOB,
+		PARAM_ERASE,
+		PARAM_PART,
+		PARAM_SD,
+		PARAM_NAND
+	};
+	int type;
+
+	for (i = 1; i < argc; i++) {
+		if (!strncmp(argv[i], "-w", 2))
+			type = PARAM_WRITE;
+		else if (!strncmp(argv[i], "-o", 2))
+			type = PARAM_OOB;
+		else if (!strncmp(argv[i], "-e", 2))
+			type = PARAM_ERASE;
+		else if (!strncmp(argv[i], "-p", 2))
+			type = PARAM_PART;
+		else	/* SD/MMC */
+			break;
+
+		tmp = strtol(argv[++i], &end, 10);
+		if (tmp % 2)
+			return -1;
+		if (tmp <= 0)
+			return -1;
+
+		if (type == PARAM_WRITE)
+			nand_writesize = tmp;
+		if (type == PARAM_OOB)
+			nand_oobsize = tmp;
+		if (type == PARAM_ERASE)
+			nand_erasesize = tmp;
+		if (type == PARAM_PART)
+			sd_sector = tmp;
+	}
+
+	if (strcmp(argv[i], "sd") && strcmp(argv[i], "nand"))
+		return -1;
+
+	if (i + 3 != argc)
+		return -1;
+
+	return i;
+}
+
+int main(int argc, char **argv)
+{
+	int infd, outfd;
+	int ret = 0;
+	int offset;
+
+	offset = parse_ops(argc, argv);
+	if (offset < 0) {
+		usage();
+		ret = 1;
+		goto err1;
+	}
+
+	infd = open(argv[offset + 1], O_RDONLY);
+	if (infd < 0) {
+		printf("Input BootStream file can not be opened\n");
+		ret = 2;
+		goto err1;
+	}
+
+	outfd = open(argv[offset + 2], O_CREAT | O_TRUNC | O_WRONLY,
+					S_IRUSR | S_IWUSR);
+	if (outfd < 0) {
+		printf("Output file can not be created\n");
+		ret = 3;
+		goto err2;
+	}
+
+	if (!strcmp(argv[offset], "sd"))
+		ret = mx28_create_sd_image(infd, outfd);
+	else if (!strcmp(argv[offset], "nand"))
+		ret = mx28_create_nand_image(infd, outfd);
+
+	close(outfd);
+err2:
+	close(infd);
+err1:
+	return ret;
+}