mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-28 08:08:45 +00:00

The DRIVEVBUS power rail of the AXP803 PMIC is mostly used to supply the USB bus power on micro USB sockets, when used in host mode. As this is a dynamic operation, and mostly we want micro USB sockets to act in client mode initially, BL31 should not actually enable this power line. However, on some boards DRIVEVBUS is used to supply power to normal USB-A sockets. Failing to activate this line there results in non-functional USB in U-Boot on those boards. For that reason we were enabling DRIVEVBUS so far, as it did not seem to cause any harm to the other boards. However it turns out that on the Pinephone (and other systems with a battery), actually enabling DRIVEVBUS unconditionally causes serious problems (reboot loop). To accommodate both use cases, without reverting to a build time option, check the default OTG configuration in the devicetree. For boards with USB-A sockets this is set to "host", on boards with micro-B sockets to "otg". Depending on this setting, we either enable DRIVEVBUS or leave it alone. This fixes TF-A on the Pinephone and potentially other battery powered devices. Change-Id: Iec0e07f218b2b4393bf4e05c3386261f8ed19e9f Signed-off-by: Andre Przywara <andre.przywara@arm.com>
195 lines
4.1 KiB
C
195 lines
4.1 KiB
C
/*
|
|
* Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <libfdt.h>
|
|
|
|
#include <common/debug.h>
|
|
#include <drivers/allwinner/axp.h>
|
|
|
|
int axp_check_id(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = axp_read(0x03);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret &= 0xcf;
|
|
if (ret != axp_chip_id) {
|
|
ERROR("PMIC: Found unknown PMIC %02x\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int axp_clrsetbits(uint8_t reg, uint8_t clr_mask, uint8_t set_mask)
|
|
{
|
|
uint8_t val;
|
|
int ret;
|
|
|
|
ret = axp_read(reg);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = (ret & ~clr_mask) | set_mask;
|
|
|
|
return axp_write(reg, val);
|
|
}
|
|
|
|
void axp_power_off(void)
|
|
{
|
|
/* Set "power disable control" bit */
|
|
axp_setbits(0x32, BIT(7));
|
|
}
|
|
|
|
/*
|
|
* Retrieve the voltage from a given regulator DTB node.
|
|
* Both the regulator-{min,max}-microvolt properties must be present and
|
|
* have the same value. Return that value in millivolts.
|
|
*/
|
|
static int fdt_get_regulator_millivolt(const void *fdt, int node)
|
|
{
|
|
const fdt32_t *prop;
|
|
uint32_t min_volt;
|
|
|
|
prop = fdt_getprop(fdt, node, "regulator-min-microvolt", NULL);
|
|
if (prop == NULL)
|
|
return -EINVAL;
|
|
min_volt = fdt32_to_cpu(*prop);
|
|
|
|
prop = fdt_getprop(fdt, node, "regulator-max-microvolt", NULL);
|
|
if (prop == NULL)
|
|
return -EINVAL;
|
|
|
|
if (fdt32_to_cpu(*prop) != min_volt)
|
|
return -EINVAL;
|
|
|
|
return min_volt / 1000;
|
|
}
|
|
|
|
static int setup_regulator(const void *fdt, int node,
|
|
const struct axp_regulator *reg)
|
|
{
|
|
uint8_t val;
|
|
int mvolt;
|
|
|
|
mvolt = fdt_get_regulator_millivolt(fdt, node);
|
|
if (mvolt < reg->min_volt || mvolt > reg->max_volt)
|
|
return -EINVAL;
|
|
|
|
val = (mvolt / reg->step) - (reg->min_volt / reg->step);
|
|
if (val > reg->split)
|
|
val = ((val - reg->split) / 2) + reg->split;
|
|
|
|
axp_write(reg->volt_reg, val);
|
|
axp_setbits(reg->switch_reg, BIT(reg->switch_bit));
|
|
|
|
INFO("PMIC: %s voltage: %d.%03dV\n", reg->dt_name,
|
|
mvolt / 1000, mvolt % 1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool should_enable_regulator(const void *fdt, int node)
|
|
{
|
|
if (fdt_getprop(fdt, node, "phandle", NULL) != NULL)
|
|
return true;
|
|
if (fdt_getprop(fdt, node, "regulator-always-on", NULL) != NULL)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool board_uses_usb0_host_mode(const void *fdt)
|
|
{
|
|
int node, length;
|
|
const char *prop;
|
|
|
|
node = fdt_node_offset_by_compatible(fdt, -1,
|
|
"allwinner,sun8i-a33-musb");
|
|
if (node < 0) {
|
|
return false;
|
|
}
|
|
|
|
prop = fdt_getprop(fdt, node, "dr_mode", &length);
|
|
if (!prop) {
|
|
return false;
|
|
}
|
|
|
|
return !strncmp(prop, "host", length);
|
|
}
|
|
|
|
void axp_setup_regulators(const void *fdt)
|
|
{
|
|
int node;
|
|
bool sw = false;
|
|
|
|
if (fdt == NULL)
|
|
return;
|
|
|
|
/* locate the PMIC DT node, bail out if not found */
|
|
node = fdt_node_offset_by_compatible(fdt, -1, axp_compatible);
|
|
if (node < 0) {
|
|
WARN("PMIC: No PMIC DT node, skipping setup\n");
|
|
return;
|
|
}
|
|
|
|
/* This applies to AXP803 only. */
|
|
if (fdt_getprop(fdt, node, "x-powers,drive-vbus-en", NULL) &&
|
|
board_uses_usb0_host_mode(fdt)) {
|
|
axp_clrbits(0x8f, BIT(4));
|
|
axp_setbits(0x30, BIT(2));
|
|
INFO("PMIC: Enabling DRIVEVBUS\n");
|
|
}
|
|
|
|
/* descend into the "regulators" subnode */
|
|
node = fdt_subnode_offset(fdt, node, "regulators");
|
|
if (node < 0) {
|
|
WARN("PMIC: No regulators DT node, skipping setup\n");
|
|
return;
|
|
}
|
|
|
|
/* iterate over all regulators to find used ones */
|
|
fdt_for_each_subnode(node, fdt, node) {
|
|
const struct axp_regulator *reg;
|
|
const char *name;
|
|
int length;
|
|
|
|
/* We only care if it's always on or referenced. */
|
|
if (!should_enable_regulator(fdt, node))
|
|
continue;
|
|
|
|
name = fdt_get_name(fdt, node, &length);
|
|
|
|
/* Enable the switch last to avoid overheating. */
|
|
if (!strncmp(name, "dc1sw", length) ||
|
|
!strncmp(name, "sw", length)) {
|
|
sw = true;
|
|
continue;
|
|
}
|
|
|
|
for (reg = axp_regulators; reg->dt_name; reg++) {
|
|
if (!strncmp(name, reg->dt_name, length)) {
|
|
setup_regulator(fdt, node, reg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* On the AXP803, if DLDO2 is enabled after DC1SW, the PMIC overheats
|
|
* and shuts down. So always enable DC1SW as the very last regulator.
|
|
*/
|
|
if (sw) {
|
|
INFO("PMIC: Enabling DC SW\n");
|
|
if (axp_chip_id == AXP803_CHIP_ID)
|
|
axp_setbits(0x12, BIT(7));
|
|
if (axp_chip_id == AXP805_CHIP_ID)
|
|
axp_setbits(0x11, BIT(7));
|
|
}
|
|
}
|