blob: a021e1c9d2912b63e1053b84c99c506194dd014f [file] [log] [blame]
/*
* 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;
}
void axp_setup_regulators(const void *fdt)
{
int node;
bool dc1sw = 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;
}
if (fdt_getprop(fdt, node, "x-powers,drive-vbus-en", NULL)) {
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);
for (reg = axp_regulators; reg->dt_name; reg++) {
if (!strncmp(name, reg->dt_name, length)) {
setup_regulator(fdt, node, reg);
break;
}
}
if (!strncmp(name, "dc1sw", length)) {
/* Delay DC1SW enablement to avoid overheating. */
dc1sw = true;
continue;
}
}
/*
* If DLDO2 is enabled after DC1SW, the PMIC overheats and shuts
* down. So always enable DC1SW as the very last regulator.
*/
if (dc1sw) {
INFO("PMIC: Enabling DC1SW\n");
axp_setbits(0x12, BIT(7));
}
}