summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/misc/Kconfig12
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/tegra186_bpmp.c257
3 files changed, 270 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 80c1558..4af1cbf 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -121,6 +121,18 @@ config PCA9551_I2C_ADDR
help
The I2C address of the PCA9551 LED controller.
+config TEGRA186_BPMP
+ bool "Enable support for the Tegra186 BPMP driver"
+ depends on TEGRA186
+ help
+ The Tegra BPMP (Boot and Power Management Processor) is a separate
+ auxiliary CPU embedded into Tegra to perform power management work,
+ and controls related features such as clocks, resets, power domains,
+ PMIC I2C bus, etc. This driver provides the core low-level
+ communication path by which feature-specific drivers (such as clock)
+ can make requests to the BPMP. This driver is similar to an MFD
+ driver in the Linux kernel.
+
config WINBOND_W83627
bool "Enable Winbond Super I/O driver"
help
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index af541c6..dac9d10 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_SANDBOX) += spltest_sandbox.o
endif
endif
obj-$(CONFIG_SANDBOX) += syscon_sandbox.o
+obj-$(CONFIG_TEGRA186_BPMP) += tegra186_bpmp.o
obj-$(CONFIG_TWL4030_LED) += twl4030_led.o
obj-$(CONFIG_FSL_IFC) += fsl_ifc.o
obj-$(CONFIG_FSL_SEC_MON) += fsl_sec_mon.o
diff --git a/drivers/misc/tegra186_bpmp.c b/drivers/misc/tegra186_bpmp.c
new file mode 100644
index 0000000..f4ddbea3
--- /dev/null
+++ b/drivers/misc/tegra186_bpmp.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2016, NVIDIA CORPORATION.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <dm/root.h>
+#include <mailbox.h>
+#include <misc.h>
+#include <asm/arch-tegra/bpmp_abi.h>
+#include <asm/arch-tegra/ivc.h>
+
+#define BPMP_IVC_FRAME_COUNT 1
+#define BPMP_IVC_FRAME_SIZE 128
+
+#define BPMP_FLAG_DO_ACK BIT(0)
+#define BPMP_FLAG_RING_DOORBELL BIT(1)
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct tegra186_bpmp {
+ struct mbox_chan mbox;
+ struct tegra_ivc ivc;
+};
+
+static int tegra186_bpmp_call(struct udevice *dev, int mrq, void *tx_msg,
+ int tx_size, void *rx_msg, int rx_size)
+{
+ struct tegra186_bpmp *priv = dev_get_priv(dev);
+ int ret, err;
+ void *ivc_frame;
+ struct mrq_request *req;
+ struct mrq_response *resp;
+ ulong start_time;
+
+ debug("%s(dev=%p, mrq=%u, tx_msg=%p, tx_size=%d, rx_msg=%p, rx_size=%d) (priv=%p)\n",
+ __func__, dev, mrq, tx_msg, tx_size, rx_msg, rx_size, priv);
+
+ if ((tx_size > BPMP_IVC_FRAME_SIZE) || (rx_size > BPMP_IVC_FRAME_SIZE))
+ return -EINVAL;
+
+ ret = tegra_ivc_write_get_next_frame(&priv->ivc, &ivc_frame);
+ if (ret) {
+ error("tegra_ivc_write_get_next_frame() failed: %d\n", ret);
+ return ret;
+ }
+
+ req = ivc_frame;
+ req->mrq = mrq;
+ req->flags = BPMP_FLAG_DO_ACK | BPMP_FLAG_RING_DOORBELL;
+ memcpy(req + 1, tx_msg, tx_size);
+
+ ret = tegra_ivc_write_advance(&priv->ivc);
+ if (ret) {
+ error("tegra_ivc_write_advance() failed: %d\n", ret);
+ return ret;
+ }
+
+ start_time = timer_get_us();
+ for (;;) {
+ ret = tegra_ivc_channel_notified(&priv->ivc);
+ if (ret) {
+ error("tegra_ivc_channel_notified() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = tegra_ivc_read_get_next_frame(&priv->ivc, &ivc_frame);
+ if (!ret)
+ break;
+
+ /* Timeout 20ms; roughly 10x current max observed duration */
+ if ((timer_get_us() - start_time) > 20 * 1000) {
+ error("tegra_ivc_read_get_next_frame() timed out (%d)\n",
+ ret);
+ return -ETIMEDOUT;
+ }
+ }
+
+ resp = ivc_frame;
+ err = resp->err;
+ if (!err && rx_msg && rx_size)
+ memcpy(rx_msg, resp + 1, rx_size);
+
+ ret = tegra_ivc_read_advance(&priv->ivc);
+ if (ret) {
+ error("tegra_ivc_write_advance() failed: %d\n", ret);
+ return ret;
+ }
+
+ if (err) {
+ error("BPMP responded with error %d\n", err);
+ /* err isn't a U-Boot error code, so don't that */
+ return -EIO;
+ }
+
+ return rx_size;
+}
+
+/**
+ * The BPMP exposes multiple different services. We create a sub-device for
+ * each separate type of service, since each device must be of the appropriate
+ * UCLASS.
+ */
+static int tegra186_bpmp_bind(struct udevice *dev)
+{
+ int ret;
+ struct udevice *child;
+
+ debug("%s(dev=%p)\n", __func__, dev);
+
+ ret = device_bind_driver_to_node(dev, "tegra186_clk", "tegra186_clk",
+ dev->of_offset, &child);
+ if (ret)
+ return ret;
+
+ ret = device_bind_driver_to_node(dev, "tegra186_reset",
+ "tegra186_reset", dev->of_offset,
+ &child);
+ if (ret)
+ return ret;
+
+ ret = device_bind_driver_to_node(dev, "tegra186_power_domain",
+ "tegra186_power_domain",
+ dev->of_offset, &child);
+ if (ret)
+ return ret;
+
+ ret = dm_scan_fdt_dev(dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static ulong tegra186_bpmp_get_shmem(struct udevice *dev, int index)
+{
+ int ret;
+ struct fdtdec_phandle_args args;
+ fdt_addr_t reg;
+
+ ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, dev->of_offset,
+ "shmem", NULL, 0, index, &args);
+ if (ret < 0) {
+ error("fdtdec_parse_phandle_with_args() failed: %d\n", ret);
+ return ret;
+ }
+
+ reg = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, args.node,
+ "reg", 0, NULL, true);
+ if (reg == FDT_ADDR_T_NONE) {
+ error("fdtdec_get_addr_size_auto_noparent() failed\n");
+ return -ENODEV;
+ }
+
+ return reg;
+}
+
+static void tegra186_bpmp_ivc_notify(struct tegra_ivc *ivc)
+{
+ struct tegra186_bpmp *priv =
+ container_of(ivc, struct tegra186_bpmp, ivc);
+ int ret;
+
+ ret = mbox_send(&priv->mbox, NULL);
+ if (ret)
+ error("mbox_send() failed: %d\n", ret);
+}
+
+static int tegra186_bpmp_probe(struct udevice *dev)
+{
+ struct tegra186_bpmp *priv = dev_get_priv(dev);
+ int ret;
+ ulong tx_base, rx_base, start_time;
+
+ debug("%s(dev=%p) (priv=%p)\n", __func__, dev, priv);
+
+ ret = mbox_get_by_index(dev, 0, &priv->mbox);
+ if (ret) {
+ error("mbox_get_by_index() failed: %d\n", ret);
+ return ret;
+ }
+
+ tx_base = tegra186_bpmp_get_shmem(dev, 0);
+ if (IS_ERR_VALUE(tx_base)) {
+ error("tegra186_bpmp_get_shmem failed for tx_base\n");
+ return tx_base;
+ }
+ rx_base = tegra186_bpmp_get_shmem(dev, 1);
+ if (IS_ERR_VALUE(rx_base)) {
+ error("tegra186_bpmp_get_shmem failed for rx_base\n");
+ return rx_base;
+ }
+ debug("shmem: rx=%lx, tx=%lx\n", rx_base, tx_base);
+
+ ret = tegra_ivc_init(&priv->ivc, rx_base, tx_base, BPMP_IVC_FRAME_COUNT,
+ BPMP_IVC_FRAME_SIZE, tegra186_bpmp_ivc_notify);
+ if (ret) {
+ error("tegra_ivc_init() failed: %d\n", ret);
+ return ret;
+ }
+
+ tegra_ivc_channel_reset(&priv->ivc);
+ start_time = timer_get_us();
+ for (;;) {
+ ret = tegra_ivc_channel_notified(&priv->ivc);
+ if (!ret)
+ break;
+
+ /* Timeout 100ms */
+ if ((timer_get_us() - start_time) > 100 * 1000) {
+ error("Initial IVC reset timed out (%d)\n", ret);
+ ret = -ETIMEDOUT;
+ goto err_free_mbox;
+ }
+ }
+
+ return 0;
+
+err_free_mbox:
+ mbox_free(&priv->mbox);
+
+ return ret;
+}
+
+static int tegra186_bpmp_remove(struct udevice *dev)
+{
+ struct tegra186_bpmp *priv = dev_get_priv(dev);
+
+ debug("%s(dev=%p) (priv=%p)\n", __func__, dev, priv);
+
+ mbox_free(&priv->mbox);
+
+ return 0;
+}
+
+static struct misc_ops tegra186_bpmp_ops = {
+ .call = tegra186_bpmp_call,
+};
+
+static const struct udevice_id tegra186_bpmp_ids[] = {
+ { .compatible = "nvidia,tegra186-bpmp" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra186_bpmp) = {
+ .name = "tegra186_bpmp",
+ .id = UCLASS_MISC,
+ .of_match = tegra186_bpmp_ids,
+ .bind = tegra186_bpmp_bind,
+ .probe = tegra186_bpmp_probe,
+ .remove = tegra186_bpmp_remove,
+ .ops = &tegra186_bpmp_ops,
+ .priv_auto_alloc_size = sizeof(struct tegra186_bpmp),
+};