/*
 * (C) Copyright 2021 rkparm Electronics Co., Ltd
 *
 * SPDX-License-Identifier:     GPL-2.0+
 */

#include <common.h>
#include <malloc.h>
#include <asm/arch/rk_atags.h>

struct rkram_part {
	char name[PART_NAME_LEN];
	ulong start;
	ulong size;
	struct list_head node;
};

static LIST_HEAD(parts_head);

static int rkram_part_init(struct blk_desc *dev_desc,
			   struct list_head *parts_head)
{
	struct rkram_part *part;
	struct tag *t;
	int i, len;
	u64 start;
	u64 size;

	if (!atags_is_available()) {
		debug("%s: No ATAGS\n", __func__);
		return -ENODATA;
	}

	t = atags_get_tag(ATAG_RAM_PARTITION);
	if (!t) {
		debug("%s: No ATAGS ramdisk partition\n", __func__);
		return -ENODATA;
	}

	INIT_LIST_HEAD(parts_head);

	for (i = 0; i < t->u.ram_part.count; i++) {
		part = malloc(sizeof(*part));
		if (!part) {
			printf("%s: No memory\n", __func__);
			break;
		}

		len = strlen(t->u.ram_part.part[i].name) + 1;
		memcpy((char *)&part->name,
		       (char *)&t->u.ram_part.part[i].name, len);
		start = t->u.ram_part.part[i].start;
		size = t->u.ram_part.part[i].size;

		if (!IS_ALIGNED(start, dev_desc->blksz)) {
			printf("%s: addr 0x%llx is not %ld align\n",
			       part->name, start, dev_desc->blksz);
			return -EINVAL;
		} else if (!IS_ALIGNED(size, dev_desc->blksz)) {
			printf("%s: size 0x%llx is not %ld align\n",
			       part->name, size, dev_desc->blksz);
			return -EINVAL;
		}

		/* bytes to blksz */
		part->start = start / dev_desc->blksz;
		part->size = size / dev_desc->blksz;
		list_add_tail(&part->node, parts_head);

		debug("%s: name=%s, start=0x%lx, size=0x%lx, blksz=0x%lx\n",
		      __func__, part->name, part->start,
		      part->size, dev_desc->blksz);
	}

	return 0;
}

static void part_print_rkram_part(struct blk_desc *dev_desc)
{
	struct list_head *node;
	struct rkram_part *p;
	int i = 0;

	if (list_empty(&parts_head)) {
		printf("No Partition Table\n");
		return;
	}

	printf("Part\tStart LBA\tSize\t\tName\n");
	list_for_each(node, &parts_head) {
		p = list_entry(node, struct rkram_part, node);
		printf("%3d\t0x%08lx\t0x%08lx\t%s\n", (i++ + 1),
		       p->start, p->size, p->name);
	}
}

static int part_get_info_rkram_part(struct blk_desc *dev_desc, int idx,
				    disk_partition_t *info)
{
	struct rkram_part *p = NULL;
	struct list_head *node;
	int part_num = 1;

	if (idx < 1) {
		printf("Invalid partition no.%d\n", idx);
		return -EINVAL;
	}

	if (list_empty(&parts_head))
		return -EINVAL;

	list_for_each(node, &parts_head) {
		p = list_entry(node, struct rkram_part, node);
		if (idx == part_num)
			break;
		part_num++;
	}

	if (part_num < idx) {
		debug("%s Invalid partition no.%d\n", __func__, idx);
		return -EINVAL;
	}

	info->start = p->start;
	info->size = p->size;
	info->blksz = dev_desc->blksz;

	sprintf((char *)info->name, "%s", p->name);
	strcpy((char *)info->type, "U-Boot");
	info->bootable = 0;

	return 0;
}

static int part_test_rkram_part(struct blk_desc *dev_desc)
{
	return rkram_part_init(dev_desc, &parts_head);
}

/*
 * Add an 'a_a_' prefix so it comes before 'dos' and after 'a_efi'
 * in the linker list. We need to check this first.
 */
U_BOOT_PART_TYPE(a_a_rkram_part) = {
	.name		= "RKRAM_PART",
	.part_type	= PART_TYPE_RKRAM,
	.max_entries	= RKRAM_ENTRY_NUMBERS,
	.get_info	= part_get_info_ptr(part_get_info_rkram_part),
	.print		= part_print_ptr(part_print_rkram_part),
	.test		= part_test_rkram_part,
};