mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-22 12:54:37 +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>
149 lines
3.8 KiB
C
149 lines
3.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright 2024 Collabora Ltd.
|
|
*
|
|
* USB Power Delivery protocol stack.
|
|
*/
|
|
|
|
#include <dm/device.h>
|
|
#include <dm/device_compat.h>
|
|
#include <dm/uclass.h>
|
|
#include <linux/err.h>
|
|
#include <usb/tcpm.h>
|
|
#include "tcpm-internal.h"
|
|
|
|
int tcpm_get_voltage(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return port->supply_voltage;
|
|
}
|
|
|
|
int tcpm_get_current(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return port->current_limit;
|
|
}
|
|
|
|
enum typec_orientation tcpm_get_orientation(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
switch (port->polarity) {
|
|
case TYPEC_POLARITY_CC1:
|
|
return TYPEC_ORIENTATION_NORMAL;
|
|
case TYPEC_POLARITY_CC2:
|
|
return TYPEC_ORIENTATION_REVERSE;
|
|
default:
|
|
return TYPEC_ORIENTATION_NONE;
|
|
}
|
|
}
|
|
|
|
const char *tcpm_get_state(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return tcpm_states[port->state];
|
|
}
|
|
|
|
int tcpm_get_pd_rev(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return port->negotiated_rev;
|
|
}
|
|
|
|
enum typec_role tcpm_get_pwr_role(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return port->pwr_role;
|
|
}
|
|
|
|
enum typec_data_role tcpm_get_data_role(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return port->data_role;
|
|
}
|
|
|
|
bool tcpm_is_connected(struct udevice *dev)
|
|
{
|
|
struct tcpm_port *port = dev_get_uclass_plat(dev);
|
|
|
|
return port->connected;
|
|
}
|
|
|
|
int tcpm_get(int index, struct udevice **devp)
|
|
{
|
|
return uclass_get_device(UCLASS_TCPM, index, devp);
|
|
}
|
|
|
|
static int tcpm_post_bind(struct udevice *dev)
|
|
{
|
|
const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
|
|
const char *cap_str;
|
|
ofnode node;
|
|
int ret;
|
|
|
|
/*
|
|
* USB Power Delivery (USB PD) specification requires, that communication
|
|
* with a sink happens within roughly 5 seconds. Otherwise the source
|
|
* might assume that the sink does not support USB PD. Starting to do
|
|
* USB PD communication after that results in a hard reset, which briefly
|
|
* removes any power from the USB-C port.
|
|
*
|
|
* On systems with alternative power supplies this is not an issue, but
|
|
* systems, which get soleley powered through their USB-C port will end
|
|
* up losing their power supply and doing a board level reset. The hard
|
|
* reset will also restart the 5 second timeout. That means a operating
|
|
* system initializing USB PD will put the system into a boot loop when
|
|
* it takes more than 5 seconds from cold boot to the operating system
|
|
* starting to transmit USB PD messages.
|
|
*
|
|
* The issue can be avoided by doing the initial USB PD communication
|
|
* in U-Boot. The operating system can then re-negotiate by doing a
|
|
* soft reset, which does not trigger removal of the supply voltage.
|
|
*
|
|
* Since the TCPM state machine is quite complex and depending on the
|
|
* remote side can take quite some time to finish, this tries to limit
|
|
* the automatic probing to systems probably relying on power being
|
|
* provided by the USB-C port(s):
|
|
*
|
|
* 1. self-powered devices won't reset when the USB-C port looses power
|
|
* 2. if the power is allowed to go into anything else than sink mode
|
|
* it is not the only power source
|
|
*/
|
|
ret = drvops->get_connector_node(dev, &node);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (ofnode_read_bool(node, "self-powered"))
|
|
return 0;
|
|
|
|
cap_str = ofnode_read_string(node, "power-role");
|
|
if (!cap_str)
|
|
return -EINVAL;
|
|
|
|
if (strcmp("sink", cap_str))
|
|
return 0;
|
|
|
|
/* Do not auto-probe PD controller when PD is disabled */
|
|
if (ofnode_read_bool(node, "pd-disable"))
|
|
return 0;
|
|
|
|
dev_info(dev, "probing Type-C port manager...");
|
|
|
|
dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
|
|
|
|
return 0;
|
|
}
|
|
|
|
UCLASS_DRIVER(tcpm) = {
|
|
.id = UCLASS_TCPM,
|
|
.name = "tcpm",
|
|
.per_device_plat_auto = sizeof(struct tcpm_port),
|
|
.post_bind = tcpm_post_bind,
|
|
.post_probe = tcpm_post_probe,
|
|
};
|