| // 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" |
| ); |