/* * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include 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)); } }