// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2020 Wind River Systems, Inc.
 *
 * Author:
 *   Bin Meng <bin.meng@windriver.com>
 *
 * A command interface to access misc devices with MISC uclass driver APIs.
 */

#include <common.h>
#include <command.h>
#include <dm.h>
#include <errno.h>
#include <misc.h>

enum misc_op {
	MISC_OP_READ,
	MISC_OP_WRITE
};

static char *misc_op_str[] = {
	"read",
	"write"
};

static int do_misc_list(struct cmd_tbl *cmdtp, int flag,
			int argc, char *const argv[])
{
	struct udevice *dev;

	printf("Device               Index     Driver\n");
	printf("-------------------------------------\n");
	for (uclass_first_device(UCLASS_MISC, &dev);
	     dev;
	     uclass_next_device(&dev)) {
		printf("%-20s %5d %10s\n", dev->name, dev_seq(dev),
		       dev->driver->name);
	}

	return 0;
}

static int do_misc_op(struct cmd_tbl *cmdtp, int flag,
		      int argc, char *const argv[], enum misc_op op)
{
	int (*misc_op)(struct udevice *, int, void *, int);
	struct udevice *dev;
	int offset;
	void *buf;
	int size;
	int ret;

	ret = uclass_get_device_by_name(UCLASS_MISC, argv[0], &dev);
	if (ret) {
		printf("Unable to find device %s\n", argv[0]);
		return ret;
	}

	offset = hextoul(argv[1], NULL);
	buf = (void *)hextoul(argv[2], NULL);
	size = hextoul(argv[3], NULL);

	if (op == MISC_OP_READ)
		misc_op = misc_read;
	else
		misc_op = misc_write;

	ret = misc_op(dev, offset, buf, size);
	if (ret < 0) {
		if (ret == -ENOSYS) {
			printf("The device does not support %s\n",
			       misc_op_str[op]);
			ret = 0;
		}
	} else {
		if (ret == size)
			ret = 0;
		else
			printf("Partially %s %d bytes\n", misc_op_str[op], ret);
	}

	return ret;
}

static int do_misc_read(struct cmd_tbl *cmdtp, int flag,
			int argc, char *const argv[])
{
	return do_misc_op(cmdtp, flag, argc, argv, MISC_OP_READ);
}

static int do_misc_write(struct cmd_tbl *cmdtp, int flag,
			 int argc, char *const argv[])
{
	return do_misc_op(cmdtp, flag, argc, argv, MISC_OP_WRITE);
}

static struct cmd_tbl misc_commands[] = {
	U_BOOT_CMD_MKENT(list, 0, 1, do_misc_list, "", ""),
	U_BOOT_CMD_MKENT(read, 4, 1, do_misc_read, "", ""),
	U_BOOT_CMD_MKENT(write, 4, 1, do_misc_write, "", ""),
};

static int do_misc(struct cmd_tbl *cmdtp, int flag,
		   int argc, char *const argv[])
{
	struct cmd_tbl *misc_cmd;
	int ret;

	if (argc < 2)
		return CMD_RET_USAGE;
	misc_cmd = find_cmd_tbl(argv[1], misc_commands,
				ARRAY_SIZE(misc_commands));
	argc -= 2;
	argv += 2;
	if (!misc_cmd || argc != misc_cmd->maxargs)
		return CMD_RET_USAGE;

	ret = misc_cmd->cmd(misc_cmd, flag, argc, argv);

	return cmd_process_error(misc_cmd, ret);
}

U_BOOT_CMD(
	misc,	6,	1,	do_misc,
	"Access miscellaneous devices with MISC uclass driver APIs",
	"list                       - list all miscellaneous devices\n"
	"misc read  name offset addr len - read `len' bytes starting at\n"
	"				  `offset' of device `name'\n"
	"				  to memory at `addr'\n"
	"misc write name offset addr len - write `len' bytes starting at\n"
	"				  `offset' of device `name'\n"
	"				  from memory at `addr'"
);
