mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-11 07:24:46 +00:00

This adds TCPM framework in preparation for fusb302 support, which can handle USB power delivery messages. This is needed to solve issues with devices, that are running from a USB-C port supporting USB-PD, but not having a battery. Such a device currently boots to the kernel without interacting with the power-supply at all. If there are no USB-PD message replies within 5 seconds, the power-supply assumes the peripheral is not capable of USB-PD. It usually takes more than 5 seconds for the system to reach the kernel and probe the I2C based fusb302 chip driver. Thus the system always runs into this state. The power-supply's solution to fix this error state is a hard reset, which involves removing the power from VBUS. Boards without a battery (or huge capacitors) will reset at this point resulting in a boot loop. This imports the TCPM framework from the kernel. The porting has originally been done by Rockchip using hardware timers and the Linux kernel's TCPM code from some years ago. I had a look at upgrading to the latest TCPM kernel code, but that beast became a lot more complex due to adding more USB-C features. I believe these features are not needed in U-Boot and with multiple kthreads and hrtimers being involved it is non-trivial to port them. Instead I worked on stripping down features from the Rockchip port to an even more basic level. Also the TCPM code has been reworked to avoid complete use of any timers (Rockchip used SoC specific hardware timers + IRQ to implement delayed work mechanism). Instead the delayed state changes are handled directly from the poll loop. Note, that (in contrast to the original Rockchip port) the state machine has the same hard reset quirk, that the kernel has - i.e. it avoids disabling the CC pin resistors for devices that are not self-powered. Without that quirk, the Radxa Rock 5B will not just end up doing a machine reset when a hard reset is triggered, but will not even recover, because the CPU will loose power and the FUSB302 will keep this state because of leak voltage arriving through the RX serial pin (assuming a serial adapter is connected). This also includes a 'tcpm' command, which can be used to get information about the current state and the negotiated voltage and current. Co-developed-by: Wang Jie <dave.wang@rock-chips.com> Signed-off-by: Wang Jie <dave.wang@rock-chips.com> Tested-by: Soeren Moch <smoch@web.de> Tested-by: Anand Moon <linux.amoon@gmail.com> Reviewed-by: Jonas Karlman <jonas@kwiboo.se> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
136 lines
3.3 KiB
C
136 lines
3.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2024 Collabora
|
|
*/
|
|
|
|
#include <command.h>
|
|
#include <errno.h>
|
|
#include <dm.h>
|
|
#include <dm/uclass-internal.h>
|
|
#include <usb/tcpm.h>
|
|
|
|
#define LIMIT_DEV 32
|
|
#define LIMIT_PARENT 20
|
|
|
|
static struct udevice *currdev;
|
|
|
|
static int do_dev(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
|
|
{
|
|
int devnum, ret;
|
|
|
|
switch (argc) {
|
|
case 2:
|
|
devnum = (int)dectoul(argv[1], NULL);
|
|
ret = tcpm_get(devnum, &currdev);
|
|
if (ret) {
|
|
log_err("Can't get TCPM %d: %d (%s)!\n", devnum, ret, errno_str(ret));
|
|
return CMD_RET_FAILURE;
|
|
}
|
|
case 1:
|
|
if (!currdev) {
|
|
log_err("TCPM device is not set!\n\n");
|
|
return CMD_RET_USAGE;
|
|
}
|
|
|
|
printf("dev: %d @ %s\n", dev_seq(currdev), currdev->name);
|
|
}
|
|
|
|
return CMD_RET_SUCCESS;
|
|
}
|
|
|
|
static int do_list(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
struct udevice *dev;
|
|
int ret, err = 0;
|
|
|
|
printf("| ID | %-*.*s| %-*.*s| %s @ %s\n",
|
|
LIMIT_DEV, LIMIT_DEV, "Name",
|
|
LIMIT_PARENT, LIMIT_PARENT, "Parent name",
|
|
"Parent uclass", "seq");
|
|
|
|
for (ret = uclass_first_device_check(UCLASS_TCPM, &dev); dev;
|
|
ret = uclass_next_device_check(&dev)) {
|
|
if (ret)
|
|
err = ret;
|
|
|
|
printf("| %2d | %-*.*s| %-*.*s| %s @ %d | status: %i\n",
|
|
dev_seq(dev),
|
|
LIMIT_DEV, LIMIT_DEV, dev->name,
|
|
LIMIT_PARENT, LIMIT_PARENT, dev->parent->name,
|
|
dev_get_uclass_name(dev->parent), dev_seq(dev->parent),
|
|
ret);
|
|
}
|
|
|
|
if (err)
|
|
return CMD_RET_FAILURE;
|
|
|
|
return CMD_RET_SUCCESS;
|
|
}
|
|
|
|
int do_print_info(struct udevice *dev)
|
|
{
|
|
enum typec_orientation orientation = tcpm_get_orientation(dev);
|
|
const char *state = tcpm_get_state(dev);
|
|
int pd_rev = tcpm_get_pd_rev(dev);
|
|
int mv = tcpm_get_voltage(dev);
|
|
int ma = tcpm_get_current(dev);
|
|
enum typec_role pwr_role = tcpm_get_pwr_role(dev);
|
|
enum typec_data_role data_role = tcpm_get_data_role(dev);
|
|
bool connected = tcpm_is_connected(dev);
|
|
|
|
if (!connected) {
|
|
printf("TCPM State: %s\n", state);
|
|
return 0;
|
|
}
|
|
|
|
printf("Orientation: %s\n", typec_orientation_name[orientation]);
|
|
printf("PD Revision: %s\n", typec_pd_rev_name[pd_rev]);
|
|
printf("Power Role: %s\n", typec_role_name[pwr_role]);
|
|
printf("Data Role: %s\n", typec_data_role_name[data_role]);
|
|
printf("Voltage: %2d.%03d V\n", mv / 1000, mv % 1000);
|
|
printf("Current: %2d.%03d A\n", ma / 1000, ma % 1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_info(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
if (!currdev) {
|
|
printf("First, set the TCPM device!\n");
|
|
return CMD_RET_USAGE;
|
|
}
|
|
|
|
return do_print_info(currdev);
|
|
}
|
|
|
|
static struct cmd_tbl subcmd[] = {
|
|
U_BOOT_CMD_MKENT(dev, 2, 1, do_dev, "", ""),
|
|
U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""),
|
|
U_BOOT_CMD_MKENT(info, 1, 1, do_info, "", ""),
|
|
};
|
|
|
|
static int do_tcpm(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
struct cmd_tbl *cmd;
|
|
|
|
argc--;
|
|
argv++;
|
|
|
|
cmd = find_cmd_tbl(argv[0], subcmd, ARRAY_SIZE(subcmd));
|
|
if (!cmd || argc > cmd->maxargs)
|
|
return CMD_RET_USAGE;
|
|
|
|
return cmd->cmd(cmdtp, flag, argc, argv);
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
U_BOOT_CMD(tcpm, CONFIG_SYS_MAXARGS, 1, do_tcpm,
|
|
"TCPM sub-system",
|
|
"list - list TCPM devices\n"
|
|
"tcpm dev [ID] - show or [set] operating TCPM device\n"
|
|
"tcpm info - dump information\n"
|
|
);
|