diff --git a/drivers/allwinner/axp/axp803.c b/drivers/allwinner/axp/axp803.c new file mode 100644 index 000000000..53b11c11a --- /dev/null +++ b/drivers/allwinner/axp/axp803.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +const uint8_t axp_chip_id = AXP803_CHIP_ID; +const char *const axp_compatible = "x-powers,axp803"; + +const struct axp_regulator axp_regulators[] = { + {"dcdc1", 1600, 3400, 100, NA, 0x20, 0x10, 0}, + {"dcdc5", 800, 1840, 10, 32, 0x24, 0x10, 4}, + {"dcdc6", 600, 1520, 10, 50, 0x25, 0x10, 5}, + {"dldo1", 700, 3300, 100, NA, 0x15, 0x12, 3}, + {"dldo2", 700, 4200, 100, 27, 0x16, 0x12, 4}, + {"dldo3", 700, 3300, 100, NA, 0x17, 0x12, 5}, + {"dldo4", 700, 3300, 100, NA, 0x18, 0x12, 6}, + {"fldo1", 700, 1450, 50, NA, 0x1c, 0x13, 2}, + {} +}; diff --git a/drivers/allwinner/axp/common.c b/drivers/allwinner/axp/common.c new file mode 100644 index 000000000..a021e1c9d --- /dev/null +++ b/drivers/allwinner/axp/common.c @@ -0,0 +1,169 @@ +/* + * 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)); + } +} diff --git a/include/drivers/allwinner/axp.h b/include/drivers/allwinner/axp.h new file mode 100644 index 000000000..f3d6277cb --- /dev/null +++ b/include/drivers/allwinner/axp.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef AXP_H +#define AXP_H + +#include + +#define NA 0xff + +enum { + AXP803_CHIP_ID = 0x41, +}; + +struct axp_regulator { + const char *dt_name; + uint16_t min_volt; + uint16_t max_volt; + uint16_t step; + unsigned char split; + unsigned char volt_reg; + unsigned char switch_reg; + unsigned char switch_bit; +}; + +extern const uint8_t axp_chip_id; +extern const char *const axp_compatible; +extern const struct axp_regulator axp_regulators[]; + +/* + * Since the PMIC can be connected to multiple bus types, + * low-level read/write functions must be provided by the platform + */ +int axp_read(uint8_t reg); +int axp_write(uint8_t reg, uint8_t val); +int axp_clrsetbits(uint8_t reg, uint8_t clr_mask, uint8_t set_mask); +#define axp_clrbits(reg, clr_mask) axp_clrsetbits(reg, clr_mask, 0) +#define axp_setbits(reg, set_mask) axp_clrsetbits(reg, 0, set_mask) + +int axp_check_id(void); +void axp_power_off(void); +void axp_setup_regulators(const void *fdt); + +#endif /* AXP_H */ diff --git a/plat/allwinner/common/include/sunxi_private.h b/plat/allwinner/common/include/sunxi_private.h index 1f410559f..dcf3dc965 100644 --- a/plat/allwinner/common/include/sunxi_private.h +++ b/plat/allwinner/common/include/sunxi_private.h @@ -12,7 +12,7 @@ void sunxi_configure_mmu_el3(int flags); void sunxi_cpu_on(u_register_t mpidr); void sunxi_cpu_off(u_register_t mpidr); void sunxi_disable_secondary_cpus(u_register_t primary_mpidr); -void __dead2 sunxi_power_down(void); +void sunxi_power_down(void); int sunxi_pmic_setup(uint16_t socid, const void *fdt); void sunxi_security_setup(void); diff --git a/plat/allwinner/common/sunxi_common.c b/plat/allwinner/common/sunxi_common.c index 0797452a6..1e21a4262 100644 --- a/plat/allwinner/common/sunxi_common.c +++ b/plat/allwinner/common/sunxi_common.c @@ -150,16 +150,16 @@ int sunxi_init_platform_r_twi(uint16_t socid, bool use_rsb) /* set both pins to pull-up */ mmio_clrsetbits_32(SUNXI_R_PIO_BASE + 0x1c, 0x0fU, 0x5U); - /* assert, then de-assert reset of I2C/RSB controller */ - mmio_clrbits_32(SUNXI_R_PRCM_BASE + reset_offset, device_bit); - mmio_setbits_32(SUNXI_R_PRCM_BASE + reset_offset, device_bit); - /* un-gate clock */ if (socid != SUNXI_SOC_H6) mmio_setbits_32(SUNXI_R_PRCM_BASE + 0x28, device_bit); else mmio_setbits_32(SUNXI_R_PRCM_BASE + 0x19c, device_bit | BIT(0)); + /* assert, then de-assert reset of I2C/RSB controller */ + mmio_clrbits_32(SUNXI_R_PRCM_BASE + reset_offset, device_bit); + mmio_setbits_32(SUNXI_R_PRCM_BASE + reset_offset, device_bit); + return 0; } diff --git a/plat/allwinner/common/sunxi_pm.c b/plat/allwinner/common/sunxi_pm.c index 13e135325..9b074d2ac 100644 --- a/plat/allwinner/common/sunxi_pm.c +++ b/plat/allwinner/common/sunxi_pm.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ @@ -65,6 +65,11 @@ static void __dead2 sunxi_system_off(void) sunxi_disable_secondary_cpus(read_mpidr()); sunxi_power_down(); + + udelay(1000); + ERROR("PSCI: Cannot turn off system, halting\n"); + wfi(); + panic(); } static void __dead2 sunxi_system_reset(void) diff --git a/plat/allwinner/sun50i_a64/platform.mk b/plat/allwinner/sun50i_a64/platform.mk index b46fbc2d1..a76a679ed 100644 --- a/plat/allwinner/sun50i_a64/platform.mk +++ b/plat/allwinner/sun50i_a64/platform.mk @@ -1,5 +1,5 @@ # -# Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. +# Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. # # SPDX-License-Identifier: BSD-3-Clause # @@ -7,4 +7,6 @@ # The differences between the platform are covered by the include files. include plat/allwinner/common/allwinner-common.mk -PLAT_BL_COMMON_SOURCES += drivers/allwinner/sunxi_rsb.c +BL31_SOURCES += drivers/allwinner/axp/axp803.c \ + drivers/allwinner/axp/common.c \ + drivers/allwinner/sunxi_rsb.c diff --git a/plat/allwinner/sun50i_a64/sunxi_power.c b/plat/allwinner/sun50i_a64/sunxi_power.c index d48ff56dc..5b7d76ae9 100644 --- a/plat/allwinner/sun50i_a64/sunxi_power.c +++ b/plat/allwinner/sun50i_a64/sunxi_power.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. * Copyright (c) 2018, Icenowy Zheng * * SPDX-License-Identifier: BSD-3-Clause @@ -7,14 +7,11 @@ #include -#include - #include -#include #include +#include #include -#include #include #include @@ -22,6 +19,7 @@ #include static enum pmic_type { + UNKNOWN, GENERIC_H5, GENERIC_A64, REF_DESIGN_H5, /* regulators controlled by GPIO pins on port L */ @@ -38,7 +36,7 @@ static enum pmic_type { * disabled. * This function only cares about peripherals. */ -void sunxi_turn_off_soc(uint16_t socid) +static void sunxi_turn_off_soc(uint16_t socid) { int i; @@ -113,175 +111,22 @@ static int rsb_init(void) return ret; /* Associate the 8-bit runtime address with the 12-bit bus address. */ - return rsb_assign_runtime_address(AXP803_HW_ADDR, - AXP803_RT_ADDR); -} - -static int axp_write(uint8_t reg, uint8_t val) -{ - return rsb_write(AXP803_RT_ADDR, reg, val); -} - -static int axp_clrsetbits(uint8_t reg, uint8_t clr_mask, uint8_t set_mask) -{ - uint8_t regval; - int ret; - - ret = rsb_read(AXP803_RT_ADDR, reg); - if (ret < 0) + ret = rsb_assign_runtime_address(AXP803_HW_ADDR, + AXP803_RT_ADDR); + if (ret) return ret; - regval = (ret & ~clr_mask) | set_mask; - - return rsb_write(AXP803_RT_ADDR, reg, regval); + return axp_check_id(); } -#define axp_clrbits(reg, clr_mask) axp_clrsetbits(reg, clr_mask, 0) -#define axp_setbits(reg, set_mask) axp_clrsetbits(reg, 0, set_mask) - -static bool should_enable_regulator(const void *fdt, int node) +int axp_read(uint8_t reg) { - if (fdt_getprop(fdt, node, "phandle", NULL) != NULL) - return true; - if (fdt_getprop(fdt, node, "regulator-always-on", NULL) != NULL) - return true; - return false; + return rsb_read(AXP803_RT_ADDR, reg); } -/* - * 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) +int axp_write(uint8_t reg, uint8_t val) { - 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; -} - -#define NO_SPLIT 0xff - -static const struct axp_regulator { - char *dt_name; - uint16_t min_volt; - uint16_t max_volt; - uint16_t step; - unsigned char split; - unsigned char volt_reg; - unsigned char switch_reg; - unsigned char switch_bit; -} regulators[] = { - {"dcdc1", 1600, 3400, 100, NO_SPLIT, 0x20, 0x10, 0}, - {"dcdc5", 800, 1840, 10, 32, 0x24, 0x10, 4}, - {"dcdc6", 600, 1520, 10, 50, 0x25, 0x10, 5}, - {"dldo1", 700, 3300, 100, NO_SPLIT, 0x15, 0x12, 3}, - {"dldo2", 700, 4200, 100, 27, 0x16, 0x12, 4}, - {"dldo3", 700, 3300, 100, NO_SPLIT, 0x17, 0x12, 5}, - {"dldo4", 700, 3300, 100, NO_SPLIT, 0x18, 0x12, 6}, - {"fldo1", 700, 1450, 50, NO_SPLIT, 0x1c, 0x13, 2}, - {} -}; - -static int setup_regulator(const void *fdt, int node, - const struct axp_regulator *reg) -{ - int mvolt; - uint8_t regval; - - if (!should_enable_regulator(fdt, node)) - return -ENOENT; - - mvolt = fdt_get_regulator_millivolt(fdt, node); - if (mvolt < reg->min_volt || mvolt > reg->max_volt) - return -EINVAL; - - regval = (mvolt / reg->step) - (reg->min_volt / reg->step); - if (regval > reg->split) - regval = ((regval - reg->split) / 2) + reg->split; - - axp_write(reg->volt_reg, regval); - if (reg->switch_reg < 0xff) - axp_setbits(reg->switch_reg, BIT(reg->switch_bit)); - - INFO("PMIC: AXP803: %s voltage: %d.%03dV\n", reg->dt_name, - mvolt / 1000, mvolt % 1000); - - return 0; -} - -static void setup_axp803_rails(const void *fdt) -{ - int node; - bool dc1sw = false; - - /* locate the PMIC DT node, bail out if not found */ - node = fdt_node_offset_by_compatible(fdt, -1, "x-powers,axp803"); - if (node < 0) { - WARN("BL31: PMIC: Cannot find AXP803 DT node, skipping initial 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: AXP803: Enabling DRIVEVBUS\n"); - } - - /* descend into the "regulators" subnode */ - node = fdt_subnode_offset(fdt, node, "regulators"); - if (node < 0) { - WARN("BL31: PMIC: Cannot find regulators subnode, skipping initial setup.\n"); - return; - } - - /* iterate over all regulators to find used ones */ - for (node = fdt_first_subnode(fdt, node); - node >= 0; - node = fdt_next_subnode(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 = 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: AXP803: Enabling DC1SW\n"); - axp_setbits(0x12, BIT(7)); - } + return rsb_write(AXP803_RT_ADDR, reg, val); } int sunxi_pmic_setup(uint16_t socid, const void *fdt) @@ -290,11 +135,16 @@ int sunxi_pmic_setup(uint16_t socid, const void *fdt) switch (socid) { case SUNXI_SOC_H5: + NOTICE("PMIC: Assuming H5 reference regulator design\n"); + pmic = REF_DESIGN_H5; - NOTICE("BL31: PMIC: Defaulting to PortL GPIO according to H5 reference design.\n"); + break; case SUNXI_SOC_A64: pmic = GENERIC_A64; + + INFO("PMIC: Probing AXP803 on RSB\n"); + ret = sunxi_init_platform_r_twi(socid, true); if (ret) return ret; @@ -304,20 +154,16 @@ int sunxi_pmic_setup(uint16_t socid, const void *fdt) return ret; pmic = AXP803_RSB; - NOTICE("BL31: PMIC: Detected AXP803 on RSB.\n"); - - if (fdt) - setup_axp803_rails(fdt); + axp_setup_regulators(fdt); break; default: - NOTICE("BL31: PMIC: No support for Allwinner %x SoC.\n", socid); return -ENODEV; } return 0; } -void __dead2 sunxi_power_down(void) +void sunxi_power_down(void) { switch (pmic) { case GENERIC_H5: @@ -355,16 +201,10 @@ void __dead2 sunxi_power_down(void) /* (Re-)init RSB in case the rich OS has disabled it. */ sunxi_init_platform_r_twi(SUNXI_SOC_A64, true); rsb_init(); - - /* Set "power disable control" bit */ - axp_setbits(0x32, BIT(7)); + axp_power_off(); break; default: break; } - udelay(1000); - ERROR("PSCI: Cannot turn off system, halting.\n"); - wfi(); - panic(); } diff --git a/plat/allwinner/sun50i_h6/platform.mk b/plat/allwinner/sun50i_h6/platform.mk index 5c21eadb2..fa1b0e598 100644 --- a/plat/allwinner/sun50i_h6/platform.mk +++ b/plat/allwinner/sun50i_h6/platform.mk @@ -1,5 +1,5 @@ # -# Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. +# Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. # # SPDX-License-Identifier: BSD-3-Clause # @@ -7,4 +7,4 @@ # The differences between the platform are covered by the include files. include plat/allwinner/common/allwinner-common.mk -PLAT_BL_COMMON_SOURCES += drivers/mentor/i2c/mi2cv.c +BL31_SOURCES += drivers/mentor/i2c/mi2cv.c diff --git a/plat/allwinner/sun50i_h6/sunxi_power.c b/plat/allwinner/sun50i_h6/sunxi_power.c index 5b5bad177..7bb266b88 100644 --- a/plat/allwinner/sun50i_h6/sunxi_power.c +++ b/plat/allwinner/sun50i_h6/sunxi_power.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. * Copyright (c) 2018, Icenowy Zheng * * SPDX-License-Identifier: BSD-3-Clause @@ -21,27 +21,33 @@ #define AXP805_ADDR 0x36 #define AXP805_ID 0x03 -enum pmic_type { - NO_PMIC, +static enum pmic_type { + UNKNOWN, AXP805, -}; - -enum pmic_type pmic; +} pmic; int axp_i2c_read(uint8_t chip, uint8_t reg, uint8_t *val) { int ret; ret = i2c_write(chip, 0, 0, ®, 1); + if (ret == 0) + ret = i2c_read(chip, 0, 0, val, 1); if (ret) - return ret; + ERROR("PMIC: Cannot read AXP805 register %02x\n", reg); - return i2c_read(chip, 0, 0, val, 1); + return ret; } int axp_i2c_write(uint8_t chip, uint8_t reg, uint8_t val) { - return i2c_write(chip, reg, 1, &val, 1); + int ret; + + ret = i2c_write(chip, reg, 1, &val, 1); + if (ret) + ERROR("PMIC: Cannot write AXP805 register %02x\n", reg); + + return ret; } static int axp805_probe(void) @@ -49,21 +55,18 @@ static int axp805_probe(void) int ret; uint8_t val; + /* Switch the AXP805 to master/single-PMIC mode. */ ret = axp_i2c_write(AXP805_ADDR, 0xff, 0x0); - if (ret) { - ERROR("PMIC: Cannot put AXP805 to master mode.\n"); - return -EPERM; - } + if (ret) + return ret; ret = axp_i2c_read(AXP805_ADDR, AXP805_ID, &val); + if (ret) + return ret; - if (!ret && ((val & 0xcf) == 0x40)) - NOTICE("PMIC: AXP805 detected\n"); - else if (ret) { - ERROR("PMIC: Cannot communicate with AXP805.\n"); - return -EPERM; - } else { - ERROR("PMIC: Non-AXP805 chip attached at AXP805's address.\n"); + val &= 0xcf; + if (val != 0x40) { + ERROR("PMIC: Found unknown PMIC %02x\n", val); return -EINVAL; } @@ -74,23 +77,25 @@ int sunxi_pmic_setup(uint16_t socid, const void *fdt) { int ret; - sunxi_init_platform_r_twi(SUNXI_SOC_H6, false); + INFO("PMIC: Probing AXP805 on I2C\n"); + + ret = sunxi_init_platform_r_twi(SUNXI_SOC_H6, false); + if (ret) + return ret; + /* initialise mi2cv driver */ i2c_init((void *)SUNXI_R_I2C_BASE); - NOTICE("PMIC: Probing AXP805\n"); - pmic = AXP805; - ret = axp805_probe(); if (ret) - pmic = NO_PMIC; - else - pmic = AXP805; + return ret; + + pmic = AXP805; return 0; } -void __dead2 sunxi_power_down(void) +void sunxi_power_down(void) { uint8_t val; @@ -106,9 +111,4 @@ void __dead2 sunxi_power_down(void) default: break; } - - udelay(1000); - ERROR("PSCI: Cannot communicate with PMIC, halting\n"); - wfi(); - panic(); }