/*
 * Copyright 2020,2021 Sony Home Entertainment & Sound Products Inc.
 */

#include <malloc.h>
#include <common.h>
#include <errno.h>
#include <command.h>
#include <dm.h>
#include <misc.h>
#include <board_id.h>
#include <asm/gpio.h>
#include <asm-generic/gpio.h>

#define DEVICE_NAME	"board_id"
#define BIT_NUM_MAX	31

DECLARE_GLOBAL_DATA_PTR;

struct board_id_platdata {
	struct gpio_desc *bid_gpios;
	int valid_bit_num;
};

static int get_board_id(cmd_tbl_t *cmdtp, int flag,
			int argc, char * const argv[])
{
	struct udevice *dev;
	int board_id;
	int ret;
	char bid[3];

	ret = uclass_get_device_by_name(UCLASS_MISC, DEVICE_NAME, &dev);
	if (ret) {
		printf("uclass_get_device_by_name failed: ret=%d\n", ret);
		return CMD_RET_FAILURE;
	}

	ret = misc_read(dev, 0, &board_id, sizeof(board_id));
	if (ret) {
		printf("misc_read failed: ret=%d\n", ret);
		return CMD_RET_FAILURE;
	}

	snprintf(bid, sizeof(bid), "%d", board_id);
	env_set("board_id", bid);
	printf("board id=%d\n", board_id);

	return CMD_RET_SUCCESS;
};

U_BOOT_CMD(
	bid,	1,	1,	get_board_id,
	"get board id",
	""
);

void initr_board_id(void)
{
	int ret;
	struct udevice *dev = NULL;

	ret = uclass_get_device_by_name(UCLASS_MISC, DEVICE_NAME, &dev);
	if (ret != 0 || !dev) {
		printf("%s: Can not probe device: name=%s, ret=%d\n",
			__func__, DEVICE_NAME, ret);
	}
}

static int board_id_read(struct udevice *dev, int offset, void *buf, int size)
{
	struct board_id_platdata *bid = dev_get_platdata(dev);
	int board_id = 0;

	board_id = dm_gpio_get_values_as_int(bid->bid_gpios, bid->valid_bit_num);
	if (board_id < 0) {
		printf("%s: dm_gpio_get_value_as_int failed: ret=%d\n", DEVICE_NAME, board_id);
		return board_id;
	}

	if (size == sizeof(board_id)) {
		(*(int *)buf) = board_id;
	} else {
		printf("%s: Buffer type must be int\n", DEVICE_NAME);
		return -EINVAL;
	}

	return 0;
}

static int board_id_probe(struct udevice *dev)
{
	struct board_id_platdata *bid = dev_get_platdata(dev);
	int board_id = 0;
	int ret;

	ret = dev_read_u32(dev, "valid-bit-num", (u32 *)&bid->valid_bit_num);
	if (ret) {
		printf("%s: dev_read_u32 failed: ret=%d\n", DEVICE_NAME, ret);
		return ret;
	} else if (bid->valid_bit_num > BIT_NUM_MAX) {
		printf("%s: valid-bit-num causes overflow\n", DEVICE_NAME);
		return -EINVAL;
	}

	bid->bid_gpios = calloc(bid->valid_bit_num, sizeof(struct gpio_desc));
	if (bid->bid_gpios == NULL) {
		printf("%s: Out of memory\n", DEVICE_NAME);
		return -ENOMEM;
	}

	ret = gpio_request_list_by_name(dev, "bid-gpios", bid->bid_gpios, bid->valid_bit_num, GPIOD_IS_IN);
	if (ret < 0) {
		printf("%s: gpio_request_list_by_name failed: ret=%d\n", DEVICE_NAME, ret);
		free(bid->bid_gpios);
		return ret;
	} else if (ret != bid->valid_bit_num) {
		printf("%s: bid-gpios is not set correctly ret=%d\n", DEVICE_NAME, ret);
		free(bid->bid_gpios);
		return -EINVAL;
	}
	printf("%s: bid-gpios requested\n", DEVICE_NAME);

	ret = board_id_read(dev, 0, &board_id, sizeof(board_id));
	if (ret) {
		free(bid->bid_gpios);
		return ret;
	}

	printf("%s: board id=%d\n", DEVICE_NAME, board_id);

	return 0;
}

static const struct misc_ops board_id_ops = {
	.read = board_id_read,
};

static const struct udevice_id board_id_ids[] = {
	{ .compatible = "sony,board-id" },
	{}
};

U_BOOT_DRIVER(board_id) = {
	.name = DEVICE_NAME,
	.id = UCLASS_MISC,
	.of_match = board_id_ids,
	.probe = board_id_probe,
	.platdata_auto_alloc_size = sizeof(struct board_id_platdata),
	.ops = &board_id_ops,
};
