diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/allwinner/axp/axp803.c | 25 | ||||
-rw-r--r-- | drivers/allwinner/axp/axp805.c | 35 | ||||
-rw-r--r-- | drivers/allwinner/axp/common.c | 212 | ||||
-rw-r--r-- | drivers/allwinner/sunxi_msgbox.c | 95 | ||||
-rw-r--r-- | drivers/allwinner/sunxi_rsb.c | 138 |
5 files changed, 505 insertions, 0 deletions
diff --git a/drivers/allwinner/axp/axp803.c b/drivers/allwinner/axp/axp803.c new file mode 100644 index 0000000..19a9549 --- /dev/null +++ b/drivers/allwinner/axp/axp803.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <drivers/allwinner/axp.h> + +const uint8_t axp_chip_id = AXP803_CHIP_ID; +const char *const axp_compatible = "x-powers,axp803"; + +#if SUNXI_SETUP_REGULATORS == 1 +const struct axp_regulator axp_regulators[] = { + {"aldo1", 700, 3300, 100, NA, 0x28, 0x13, 5}, + {"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}, + {} +}; +#endif diff --git a/drivers/allwinner/axp/axp805.c b/drivers/allwinner/axp/axp805.c new file mode 100644 index 0000000..3a03fec --- /dev/null +++ b/drivers/allwinner/axp/axp805.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <drivers/allwinner/axp.h> + +const uint8_t axp_chip_id = AXP805_CHIP_ID; +const char *const axp_compatible = "x-powers,axp805"; + +#if SUNXI_SETUP_REGULATORS == 1 +/* + * The "dcdcd" split changes the step size by a factor of 5, not 2; + * disallow values above the split to maintain accuracy. + */ +const struct axp_regulator axp_regulators[] = { + {"dcdca", 600, 1520, 10, 50, 0x12, 0x10, 0}, + {"dcdcb", 1000, 2550, 50, NA, 0x13, 0x10, 1}, + {"dcdcc", 600, 1520, 10, 50, 0x14, 0x10, 2}, + {"dcdcd", 600, 1500, 20, NA, 0x15, 0x10, 3}, + {"dcdce", 1100, 3400, 100, NA, 0x16, 0x10, 4}, + {"aldo1", 700, 3300, 100, NA, 0x17, 0x10, 5}, + {"aldo2", 700, 3300, 100, NA, 0x18, 0x10, 6}, + {"aldo3", 700, 3300, 100, NA, 0x19, 0x10, 7}, + {"bldo1", 700, 1900, 100, NA, 0x20, 0x11, 0}, + {"bldo2", 700, 1900, 100, NA, 0x21, 0x11, 1}, + {"bldo3", 700, 1900, 100, NA, 0x22, 0x11, 2}, + {"bldo4", 700, 1900, 100, NA, 0x23, 0x11, 3}, + {"cldo1", 700, 3300, 100, NA, 0x24, 0x11, 4}, + {"cldo2", 700, 4200, 100, 27, 0x25, 0x11, 5}, + {"cldo3", 700, 3300, 100, NA, 0x26, 0x11, 6}, + {} +}; +#endif diff --git a/drivers/allwinner/axp/common.c b/drivers/allwinner/axp/common.c new file mode 100644 index 0000000..f1250b0 --- /dev/null +++ b/drivers/allwinner/axp/common.c @@ -0,0 +1,212 @@ +/* + * 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)); +} + +#if SUNXI_SETUP_REGULATORS == 1 +/* + * 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 is_node_disabled(const void *fdt, int node) +{ + const char *cell; + cell = fdt_getprop(fdt, node, "status", NULL); + if (cell == NULL) { + return false; + } + return strcmp(cell, "okay") != 0; +} + +static bool should_enable_regulator(const void *fdt, int node) +{ + if (is_node_disabled(fdt, node)) { + return false; + } + 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)); + } +} +#endif /* SUNXI_SETUP_REGULATORS */ diff --git a/drivers/allwinner/sunxi_msgbox.c b/drivers/allwinner/sunxi_msgbox.c new file mode 100644 index 0000000..cc4a6ff --- /dev/null +++ b/drivers/allwinner/sunxi_msgbox.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdbool.h> + +#include <drivers/delay_timer.h> +#include <lib/bakery_lock.h> +#include <lib/mmio.h> +#include <lib/utils_def.h> + +#include <sunxi_mmap.h> + +#define REMOTE_IRQ_EN_REG 0x0040 +#define REMOTE_IRQ_STAT_REG 0x0050 +#define LOCAL_IRQ_EN_REG 0x0060 +#define LOCAL_IRQ_STAT_REG 0x0070 + +#define RX_IRQ(n) BIT(0 + 2 * (n)) +#define TX_IRQ(n) BIT(1 + 2 * (n)) + +#define FIFO_STAT_REG(n) (0x0100 + 0x4 * (n)) +#define FIFO_STAT_MASK GENMASK(0, 0) + +#define MSG_STAT_REG(n) (0x0140 + 0x4 * (n)) +#define MSG_STAT_MASK GENMASK(2, 0) + +#define MSG_DATA_REG(n) (0x0180 + 0x4 * (n)) + +#define RX_CHAN 1 +#define TX_CHAN 0 + +#define MHU_MAX_SLOT_ID 31 + +#define MHU_TIMEOUT_DELAY 10 +#define MHU_TIMEOUT_ITERS 10000 + +static DEFINE_BAKERY_LOCK(mhu_secure_message_lock); + +static bool sunxi_msgbox_last_tx_done(unsigned int chan) +{ + uint32_t stat = mmio_read_32(SUNXI_MSGBOX_BASE + REMOTE_IRQ_STAT_REG); + + return (stat & RX_IRQ(chan)) == 0U; +} + +static bool sunxi_msgbox_peek_data(unsigned int chan) +{ + uint32_t stat = mmio_read_32(SUNXI_MSGBOX_BASE + MSG_STAT_REG(chan)); + + return (stat & MSG_STAT_MASK) != 0U; +} + +void mhu_secure_message_start(unsigned int slot_id __unused) +{ + uint32_t timeout = MHU_TIMEOUT_ITERS; + + bakery_lock_get(&mhu_secure_message_lock); + + /* Wait for all previous messages to be acknowledged. */ + while (!sunxi_msgbox_last_tx_done(TX_CHAN) && --timeout) + udelay(MHU_TIMEOUT_DELAY); +} + +void mhu_secure_message_send(unsigned int slot_id) +{ + mmio_write_32(SUNXI_MSGBOX_BASE + MSG_DATA_REG(TX_CHAN), BIT(slot_id)); +} + +uint32_t mhu_secure_message_wait(void) +{ + uint32_t timeout = MHU_TIMEOUT_ITERS; + uint32_t msg = 0; + + /* Wait for a message from the SCP. */ + while (!sunxi_msgbox_peek_data(RX_CHAN) && --timeout) + udelay(MHU_TIMEOUT_DELAY); + + /* Return the most recent message in the FIFO. */ + while (sunxi_msgbox_peek_data(RX_CHAN)) + msg = mmio_read_32(SUNXI_MSGBOX_BASE + MSG_DATA_REG(RX_CHAN)); + + return msg; +} + +void mhu_secure_message_end(unsigned int slot_id) +{ + /* Acknowledge a response by clearing the IRQ status. */ + mmio_write_32(SUNXI_MSGBOX_BASE + LOCAL_IRQ_STAT_REG, RX_IRQ(RX_CHAN)); + + bakery_lock_release(&mhu_secure_message_lock); +} diff --git a/drivers/allwinner/sunxi_rsb.c b/drivers/allwinner/sunxi_rsb.c new file mode 100644 index 0000000..67f5b7e --- /dev/null +++ b/drivers/allwinner/sunxi_rsb.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017-2018 ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <errno.h> + +#include <common/debug.h> +#include <drivers/delay_timer.h> +#include <lib/mmio.h> + +#include <sunxi_mmap.h> + +#define RSB_CTRL 0x00 +#define RSB_CCR 0x04 +#define RSB_INTE 0x08 +#define RSB_STAT 0x0c +#define RSB_DADDR0 0x10 +#define RSB_DLEN 0x18 +#define RSB_DATA0 0x1c +#define RSB_LCR 0x24 +#define RSB_PMCR 0x28 +#define RSB_CMD 0x2c +#define RSB_SADDR 0x30 + +#define RSBCMD_SRTA 0xE8 +#define RSBCMD_RD8 0x8B +#define RSBCMD_RD16 0x9C +#define RSBCMD_RD32 0xA6 +#define RSBCMD_WR8 0x4E +#define RSBCMD_WR16 0x59 +#define RSBCMD_WR32 0x63 + +#define MAX_TRIES 100000 + +static int rsb_wait_bit(const char *desc, unsigned int offset, uint32_t mask) +{ + uint32_t reg, tries = MAX_TRIES; + + do + reg = mmio_read_32(SUNXI_R_RSB_BASE + offset); + while ((reg & mask) && --tries); /* transaction in progress */ + if (reg & mask) { + ERROR("%s: timed out\n", desc); + return -ETIMEDOUT; + } + + return 0; +} + +static int rsb_wait_stat(const char *desc) +{ + uint32_t reg; + int ret = rsb_wait_bit(desc, RSB_CTRL, BIT(7)); + + if (ret) + return ret; + + reg = mmio_read_32(SUNXI_R_RSB_BASE + RSB_STAT); + if (reg == 0x01) + return 0; + + ERROR("%s: 0x%x\n", desc, reg); + return -reg; +} + +/* Initialize the RSB controller. */ +int rsb_init_controller(void) +{ + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CTRL, 0x01); /* soft reset */ + + return rsb_wait_bit("RSB: reset controller", RSB_CTRL, BIT(0)); +} + +int rsb_read(uint8_t rt_addr, uint8_t reg_addr) +{ + int ret; + + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CMD, RSBCMD_RD8); /* read a byte */ + mmio_write_32(SUNXI_R_RSB_BASE + RSB_SADDR, rt_addr << 16); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_DADDR0, reg_addr); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CTRL, 0x80);/* start transaction */ + + ret = rsb_wait_stat("RSB: read command"); + if (ret) + return ret; + + return mmio_read_32(SUNXI_R_RSB_BASE + RSB_DATA0) & 0xff; /* result */ +} + +int rsb_write(uint8_t rt_addr, uint8_t reg_addr, uint8_t value) +{ + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CMD, RSBCMD_WR8); /* byte write */ + mmio_write_32(SUNXI_R_RSB_BASE + RSB_SADDR, rt_addr << 16); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_DADDR0, reg_addr); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_DATA0, value); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CTRL, 0x80);/* start transaction */ + + return rsb_wait_stat("RSB: write command"); +} + +int rsb_set_device_mode(uint32_t device_mode) +{ + mmio_write_32(SUNXI_R_RSB_BASE + RSB_PMCR, + (device_mode & 0x00ffffff) | BIT(31)); + + return rsb_wait_bit("RSB: set device to RSB", RSB_PMCR, BIT(31)); +} + +int rsb_set_bus_speed(uint32_t source_freq, uint32_t bus_freq) +{ + uint32_t reg; + + if (bus_freq == 0) + return -EINVAL; + + reg = source_freq / bus_freq; + if (reg < 2) + return -EINVAL; + + reg = reg / 2 - 1; + reg |= (1U << 8); /* one cycle of CD output delay */ + + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CCR, reg); + + return 0; +} + +/* Initialize the RSB PMIC connection. */ +int rsb_assign_runtime_address(uint16_t hw_addr, uint8_t rt_addr) +{ + mmio_write_32(SUNXI_R_RSB_BASE + RSB_SADDR, hw_addr | (rt_addr << 16)); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CMD, RSBCMD_SRTA); + mmio_write_32(SUNXI_R_RSB_BASE + RSB_CTRL, 0x80); + + return rsb_wait_stat("RSB: set run-time address"); +} |