/*
 * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
 * Copyright 2017 NXP
 *
 * SPDX-License-Identifier:     GPL-2.0+
 */

#include <fsl_fastboot.h>
#include <linux/stat.h>
#include <linux/types.h>
#include <common.h>
#include <g_dnl.h>
#include <mmc.h>
#include "bcb.h"
#define ALIGN_BYTES 64 /*armv7 cache line need 64 bytes aligned */

static ulong get_block_size(char *ifname, int dev)
{
	struct blk_desc *dev_desc = NULL;

	dev_desc = blk_get_dev(ifname, dev);
	if (dev_desc == NULL) {
		printf("Block device %s %d not supported\n", ifname, dev);
		return 0;
	}

	return dev_desc->blksz;
}

static int do_write(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	char *ep;
	struct blk_desc *dev_desc = NULL;
	int dev;
	int part = 0;
	disk_partition_t part_info;
	ulong offset = 0u;
	ulong limit = 0u;
	void *addr;
	uint blk;
	uint cnt;

	if (argc != 6) {
		cmd_usage(cmdtp);
		return 1;
	}

	dev = (int)simple_strtoul(argv[2], &ep, 16);
	if (*ep) {
		if (*ep != ':') {
			printf("Invalid block device %s\n", argv[2]);
			return 1;
		}
		part = (int)simple_strtoul(++ep, NULL, 16);
	}

	dev_desc = blk_get_dev(argv[1], dev);
	if (dev_desc == NULL) {
		printf("Block device %s %d not supported\n", argv[1], dev);
		return 1;
	}

	addr = (void *)simple_strtoul(argv[3], NULL, 16);
	blk = simple_strtoul(argv[4], NULL, 16);
	cnt = simple_strtoul(argv[5], NULL, 16);

	if (part != 0) {
		if (part_get_info(dev_desc, part, &part_info)) {
			printf("Cannot find partition %d\n", part);
			return 1;
		}
		offset = part_info.start;
		limit = part_info.size;
	} else {
		/* Largest address not available in block_dev_desc_t. */
		limit = ~0;
	}

	if (cnt + blk > limit) {
		printf("Write out of range\n");
		return 1;
	}

	if (blk_dwrite(dev_desc, offset + blk, cnt, addr) != cnt) {
		printf("Error writing blocks\n");
		return 1;
	}

	return 0;
}

U_BOOT_CMD(
	write,	6,	0,	do_write,
	"write binary data to a partition",
	"<interface> <dev[:part]> addr blk# cnt"
);

int bcb_rw_block(bool bread, char **ppblock,
		uint *pblksize, char *pblock_write, uint offset, uint size)
{
	int ret;
	char *argv[6];
	char addr_str[20];
	char cnt_str[8];
	char devpart_str[8];
	char block_begin_str[8];
	ulong blk_size = 0;
	uint blk_begin = 0;
	uint blk_end = 0;
	uint block_cnt = 0;
	char *p_block = NULL;
	unsigned int mmc_id;

	if (bread && ((ppblock == NULL) || (pblksize == NULL)))
		return -1;

	if (!bread && (pblock_write == NULL))
		return -1;

	mmc_id = mmc_get_env_dev();
	blk_size = get_block_size("mmc", mmc_id);
	if (blk_size == 0) {
		printf("bcb_rw_block, get_block_size return 0\n");
		return -1;
	}

	blk_begin = offset/blk_size;
	blk_end = (offset + size)/blk_size;
	block_cnt = 1 + (blk_end - blk_begin);

	sprintf(devpart_str, "0x%x:0x%x", mmc_id,
			fastboot_flash_find_index(FASTBOOT_PARTITION_MISC));
	sprintf(block_begin_str, "0x%x", blk_begin);
	sprintf(cnt_str, "0x%x", block_cnt);

	argv[0] = "rw"; /* not care */
	argv[1] = "mmc";
	argv[2] = devpart_str;
	argv[3] = addr_str;
	argv[4] = block_begin_str;
	argv[5] = cnt_str;

	if (bread) {
		p_block = (char *)memalign(ALIGN_BYTES, blk_size * block_cnt);
		if (NULL == p_block) {
			printf("bcb_rw_block, memalign %d bytes failed\n",
			(int)(blk_size * block_cnt));
			return -1;
		}
		sprintf(addr_str, "0x%x", (unsigned int)(uintptr_t)p_block);
		ret = do_raw_read(NULL, 0, 6, argv);
		if (ret) {
			free(p_block);
			printf("do_raw_read failed, ret %d\n", ret);
			return -1;
		}

		*ppblock = p_block;
		*pblksize = (uint)blk_size;
	} else {
		sprintf(addr_str, "0x%x", (unsigned int)(uintptr_t)pblock_write);
		ret = do_write(NULL, 0, 6, argv);
		if (ret) {
			printf("do_write failed, ret %d\n", ret);
			return -1;
		}
	}
	return 0;
}
