summaryrefslogtreecommitdiffstats
path: root/drivers/allwinner
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/allwinner')
-rw-r--r--drivers/allwinner/axp/axp803.c25
-rw-r--r--drivers/allwinner/axp/axp805.c35
-rw-r--r--drivers/allwinner/axp/common.c203
-rw-r--r--drivers/allwinner/sunxi_msgbox.c95
-rw-r--r--drivers/allwinner/sunxi_rsb.c138
5 files changed, 496 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..79f9089
--- /dev/null
+++ b/drivers/allwinner/axp/common.c
@@ -0,0 +1,203 @@
+/*
+ * 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 <common/fdt_wrappers.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 should_enable_regulator(const void *fdt, int node)
+{
+ if (!fdt_node_is_enabled(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");
+}