// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd
 */

#include <common.h>
#include <environment.h>
#include <memalign.h>
#include <boot_rkimg.h>

#define __STR(X) #X
#define STR(X) __STR(X)

#if defined(CONFIG_ENV_SIZE_REDUND) &&  \
	(CONFIG_ENV_SIZE_REDUND != CONFIG_ENV_SIZE)
#error CONFIG_ENV_SIZE_REDUND should be the same as CONFIG_ENV_SIZE
#endif

DECLARE_GLOBAL_DATA_PTR;

int get_env_addr(struct blk_desc *blk_desc, int copy, u32 *env_addr)
{
	s64 offset = CONFIG_ENV_OFFSET;

#if defined(CONFIG_ENV_OFFSET_REDUND)
	if (copy)
		offset = CONFIG_ENV_OFFSET_REDUND;
#endif
	if (offset < 0)
		return -EINVAL;

	*env_addr = offset;

	return 0;
}

int get_env_dev(void)
{
	return CONFIG_SYS_MMC_ENV_DEV;
}

#ifdef CONFIG_SYS_MMC_ENV_PART
static unsigned char env_org_hwpart;

int get_env_part(void)
{
	return CONFIG_SYS_MMC_ENV_PART;
}

static const char *init_blk_hwpart_for_env(struct blk_desc *blk_desc)
{
	enum if_type if_type;
	const char *devtype;
	int devnum, devpart, ret;

	devtype = env_get("devtype");
	devnum = env_get_ulong("devnum", 10, 0);
	devpart = get_env_part();
	if_type = if_typename_to_iftype(devtype);

	env_org_hwpart = blk_desc->hwpart;
	ret = blk_select_hwpart_devnum(if_type, devnum, devpart);
	if (ret)
		return "!Partition switch failed";

	return NULL;
}

static void fini_blk_hwpart_for_env(void)
{
	enum if_type if_type;
	const char *devtype;
	int devnum;

	devtype = env_get("devtype");
	devnum = env_get_ulong("devnum", 10, 0);
	if_type = if_typename_to_iftype(devtype);

	blk_select_hwpart_devnum(if_type, devnum, env_org_hwpart);
}
#else
static inline const char *init_blk_hwpart_for_env(struct blk_desc *blk_desc)
{ return NULL; }
static inline void fini_blk_hwpart_for_env(void) {}
#endif

#if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_SPL_BUILD)
static inline int write_env(struct blk_desc *blk_desc, unsigned long size,
			    unsigned long offset, const void *buffer)
{
	uint blk_start, blk_cnt, n;

	blk_start = ALIGN(offset, blk_desc->blksz) / blk_desc->blksz;
	blk_cnt	  = ALIGN(size, blk_desc->blksz) / blk_desc->blksz;

	n = blk_dwrite(blk_desc, blk_start, blk_cnt, (u_char *)buffer);

	return (n == blk_cnt) ? 0 : -1;
}

static int env_blk_save(void)
{
	ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
	struct blk_desc *blk_desc;
	const char *errmsg = NULL;
	int ret, copy = 0;
	u32 offset;

	blk_desc = rockchip_get_bootdev();
	if (!blk_desc) {
		puts("Can't find bootdev\n");
		return -EIO;
	}

	errmsg = init_blk_hwpart_for_env(blk_desc);
	if (errmsg) {
		puts(errmsg);
		return -EIO;
	}

	ret = env_export(env_new);
	if (ret)
		goto fini;

#ifdef CONFIG_ENV_OFFSET_REDUND
	if (gd->env_valid == ENV_VALID)
		copy = 1;
#endif

	if (get_env_addr(blk_desc, copy, &offset)) {
		ret = 1;
		goto fini;
	}

	printf("Writing to %s%s(%s)... ", copy ? "redundant " : "",
	       env_get("devtype"), env_get("devnum"));

	if (write_env(blk_desc, CONFIG_ENV_SIZE, offset, (u_char *)env_new)) {
		puts("failed\n");
		ret = 1;
		goto fini;
	}

	puts("done\n");
	ret = 0;

#ifdef CONFIG_ENV_OFFSET_REDUND
	gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID : ENV_REDUND;
#endif

fini:
	fini_blk_hwpart_for_env();

	return ret;
}
#endif /* CONFIG_CMD_SAVEENV && !CONFIG_SPL_BUILD */

static inline int read_env(struct blk_desc *blk_desc, unsigned long size,
			   unsigned long offset, const void *buffer)
{
	uint blk_start, blk_cnt, n;

	blk_start = ALIGN(offset, blk_desc->blksz) / blk_desc->blksz;
	blk_cnt	  = ALIGN(size, blk_desc->blksz) / blk_desc->blksz;

	n = blk_dread(blk_desc, blk_start, blk_cnt, (uchar *)buffer);

	return (n == blk_cnt) ? 0 : -1;
}

#ifdef CONFIG_ENV_OFFSET_REDUND
static int env_blk_load(void)
{
#if !defined(ENV_IS_EMBEDDED)
	struct blk_desc *blk_desc;
	const char *errmsg = NULL;
	int read1_fail = 0, read2_fail = 0;
	u32 offset1, offset2;
	int ret;

	ALLOC_CACHE_ALIGN_BUFFER(env_t, tmp_env1, 1);
	ALLOC_CACHE_ALIGN_BUFFER(env_t, tmp_env2, 1);

	blk_desc = rockchip_get_bootdev();
	if (!blk_desc) {
		puts("Can't find bootdev\n");
		return -EIO;
	}

	errmsg = init_blk_hwpart_for_env(blk_desc);
	if (errmsg) {
		ret = -EIO;
		goto err;
	}

	if (get_env_addr(blk_desc, 0, &offset1) ||
	    get_env_addr(blk_desc, 1, &offset2)) {
		ret = -EIO;
		goto fini;
	}

	read1_fail = read_env(blk_desc, CONFIG_ENV_SIZE, offset1, tmp_env1);
	read2_fail = read_env(blk_desc, CONFIG_ENV_SIZE, offset2, tmp_env2);

	if (read1_fail && read2_fail)
		puts("*** Error - No Valid Environment Area found\n");
	else if (read1_fail || read2_fail)
		puts("*** Warning - some problems detected "
		     "reading environment; recovered successfully\n");

	if (read1_fail && read2_fail) {
		errmsg = "!bad CRC";
		ret = -EIO;
		goto fini;
	} else if (!read1_fail && read2_fail) {
		gd->env_valid = ENV_VALID;
		env_import((char *)tmp_env1, 1);
	} else if (read1_fail && !read2_fail) {
		gd->env_valid = ENV_REDUND;
		env_import((char *)tmp_env2, 1);
	} else {
		env_import_redund((char *)tmp_env1, (char *)tmp_env2);
	}

	ret = 0;

fini:
	fini_blk_hwpart_for_env();
err:
	if (ret)
		set_default_env(errmsg);

#endif
	return ret;
}
#else /* ! CONFIG_ENV_OFFSET_REDUND */
static int env_blk_load(void)
{
#if !defined(ENV_IS_EMBEDDED)
	ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
	struct blk_desc *blk_desc;
	const char *errmsg = NULL;
	u32 offset;
	int ret;

	blk_desc = rockchip_get_bootdev();
	if (!blk_desc) {
		puts("Can't find bootdev\n");
		return -EIO;
	}

	errmsg = init_blk_hwpart_for_env(blk_desc);
	if (errmsg) {
		ret = -EIO;
		puts(errmsg);
		goto err;
	}

	if (get_env_addr(blk_desc, 0, &offset)) {
		ret = -EIO;
		goto fini;
	}

	if (read_env(blk_desc, CONFIG_ENV_SIZE, offset, buf)) {
		errmsg = "!read failed";
		ret = -EIO;
		goto fini;
	}

	env_import(buf, 1);
	ret = 0;

fini:
	fini_blk_hwpart_for_env();
err:
	if (ret)
		set_default_env(errmsg);
#endif
	return ret;
}
#endif /* CONFIG_ENV_OFFSET_REDUND */

U_BOOT_ENV_LOCATION(env_blk) = {
	.location	= ENVL_BLK,
	ENV_NAME("ENV_BLK")
	.load		= env_blk_load,
#ifndef CONFIG_SPL_BUILD
	.save		= env_save_ptr(env_blk_save),
#endif
};