Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2025 Altera Corporation <www.altera.com>
 */

#include <bootretry.h>
#include <cli.h>
#include <command.h>
#include <console.h>
#include <dm.h>
#include <dw-i3c.h>
#include <edid.h>
#include <errno.h>
#include <hexdump.h>
#include <log.h>
#include <malloc.h>
#include <asm/byteorder.h>
#include <linux/compiler.h>
#include <linux/delay.h>
#include <u-boot/crc.h>
#include <linux/i3c/master.h>
#include <linux/printk.h>
#include <linux/types.h>

static struct udevice *currdev;
static struct udevice *prevdev;
static struct dw_i3c_master *master;

static void low_to_high_bytes(void *data, size_t size)
{
	u8 *byte_data = data;
	size_t start = 0;
	size_t end = size - 1;

	while (start < end) {
		u8 temp = byte_data[start];

		byte_data[start] = byte_data[end];
		byte_data[end] = temp;
		start++;
		end--;
	}
}

static int handle_i3c_select(const char *name)
{
	struct uclass *uc;
	struct udevice *dev_list;
	int ret = uclass_get_device_by_name(UCLASS_I3C, name, &currdev);

	if (ret) {
		currdev = prevdev;
		if (!currdev) {
			ret = uclass_get(UCLASS_I3C, &uc);
			if (ret)
				return CMD_RET_FAILURE;

			uclass_foreach_dev(dev_list, uc)
				printf("%s (%s)\n", dev_list->name, dev_list->driver->name);

			printf("i3c: Host controller not initialized: %s\n", name);
			return CMD_RET_FAILURE;
		}
	} else {
		master = dev_get_priv(currdev);
		printf("i3c: Current controller: %s\n", currdev->name);
		prevdev = currdev;
	}

	return CMD_RET_SUCCESS;
}

static int handle_i3c_list(void)
{
	struct uclass *uc;
	struct udevice *dev_list;
	int ret = uclass_get(UCLASS_I3C, &uc);

	if (ret)
		return CMD_RET_FAILURE;

	uclass_foreach_dev(dev_list, uc)
		printf("%s (%s)\n", dev_list->name, dev_list->driver->name);

	return CMD_RET_SUCCESS;
}

static int handle_i3c_current(void)
{
	if (!currdev)
		printf("i3c: No current controller selected\n");
	else
		printf("i3c: Current controller: %s\n", currdev->name);

	return CMD_RET_SUCCESS;
}

static int handle_i3c_device_list(void)
{
	if (!master) {
		printf("i3c: No controller active\n");
		return CMD_RET_FAILURE;
	}

	for (int i = 0; i < master->num_i3cdevs; i++) {
		struct i3c_device_info *info = &master->i3cdev[i]->info;

		printf("Device %d:\n", i);
		printf("  Static Address  : 0x%02X\n", info->static_addr);
		printf("  Dynamic Address : 0x%X\n", info->dyn_addr);
		printf("  PID             : %016llx\n", info->pid);
		printf("  BCR             : 0x%X\n", info->bcr);
		printf("  DCR             : 0x%X\n", info->dcr);
		printf("  Max Read DS     : 0x%X\n", info->max_read_ds);
		printf("  Max Write DS    : 0x%X\n", info->max_write_ds);
		printf("\n");
	}

	return CMD_RET_SUCCESS;
}

static int handle_i3c_write(int argc, char *const argv[])
{
	u32 mem_addr, num_bytes, dev_num_val;
	u8 device_num;
	u8 *data;
	int ret;

	if (argc < 5)
		return CMD_RET_USAGE;

	if (!currdev) {
		printf("i3c: No I3C controller selected\n");
		return CMD_RET_FAILURE;
	}

	mem_addr = hextoul(argv[2], NULL);
	num_bytes = hextoul(argv[3], NULL);
	dev_num_val = hextoul(argv[4], NULL);

	if (num_bytes == 0 || num_bytes > 4) {
		printf("i3c: Length must be between 1 and 4\n");
		return CMD_RET_USAGE;
	}

	if (dev_num_val > 0xFF) {
		printf("i3c: Device number 0x%x exceeds valid u8 range\n", dev_num_val);
		return CMD_RET_USAGE;
	}

	device_num = dev_num_val;
	data = malloc(num_bytes);

	if (!data) {
		printf("i3c: Memory allocation failed\n");
		return -ENOMEM;
	}

	memcpy(data, (void *)(uintptr_t)mem_addr, num_bytes);
	low_to_high_bytes(data, num_bytes);

	ret = dm_i3c_write(currdev, device_num, data, num_bytes);

	if (ret)
		printf("i3c: Write failed: %d\n", ret);

	free(data);
	return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS;
}

static int handle_i3c_read(int argc, char *const argv[])
{
	u32 mem_addr, read_len, dev_num_val;
	u8 device_num;
	u8 *rdata;
	int ret;

	if (argc < 5)
		return CMD_RET_USAGE;

	if (!currdev) {
		printf("i3c: No I3C controller selected\n");
		return CMD_RET_FAILURE;
	}

	mem_addr = hextoul(argv[2], NULL);
	read_len = hextoul(argv[3], NULL);
	dev_num_val = hextoul(argv[4], NULL);

	if (read_len == 0) {
		printf("i3c: Read length must be greater than 0\n");
		return CMD_RET_USAGE;
	}

	if (dev_num_val > 0xFF) {
		printf("i3c: Device number 0x%x exceeds valid u8 range\n", dev_num_val);
		return CMD_RET_USAGE;
	}

	device_num = dev_num_val;
	rdata = malloc(read_len);

	if (!rdata) {
		printf("i3c: Memory allocation failed\n");
		return -ENOMEM;
	}

	ret = dm_i3c_read(currdev, device_num, rdata, read_len);

	if (ret) {
		printf("i3c: Read failed: %d\n", ret);
		free(rdata);
		return CMD_RET_FAILURE;
	}

	memcpy((void *)(uintptr_t)mem_addr, rdata, read_len);
	print_hex_dump("i3c read: ", DUMP_PREFIX_OFFSET, 16, 1,
		       (void *)(uintptr_t)mem_addr, read_len, false);

	free(rdata);
	return CMD_RET_SUCCESS;
}

static bool is_i3c_subcommand(const char *cmd)
{
	return !strcmp(cmd, "write") ||
	       !strcmp(cmd, "read") ||
	       !strcmp(cmd, "device_list") ||
	       !strcmp(cmd, "list") ||
	       !strcmp(cmd, "current");
}

static int do_i3c(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
	if (argc < 2)
		return CMD_RET_USAGE;

	const char *subcmd = argv[1];

	if (!is_i3c_subcommand(subcmd))
		return handle_i3c_select(subcmd);

	if (!currdev) {
		printf("i3c: No I3C controller selected\n");
		return CMD_RET_FAILURE;
	}

	if (!strcmp(subcmd, "list"))
		return handle_i3c_list();
	else if (!strcmp(subcmd, "current"))
		return handle_i3c_current();
	else if (!strcmp(subcmd, "device_list"))
		return handle_i3c_device_list();
	else if (!strcmp(subcmd, "write"))
		return handle_i3c_write(argc, argv);
	else if (!strcmp(subcmd, "read"))
		return handle_i3c_read(argc, argv);

	return CMD_RET_USAGE;
}

U_BOOT_CMD(
	i3c, 5, 1, do_i3c,
	"access the system i3c",
	"i3c write <mem_addr> <length> <device_number> - write from memory to device\n"
	"i3c read <mem_addr> <length> <device_number> - read from device to memory\n"
	"i3c device_list - List valid target devices\n"
	"i3c <host_controller> - Select i3c controller\n"
	"i3c list - List all available i3c controllers\n"
	"i3c current - Show current i3c controller"
);