173dd5c4cSStephen Warren /* 273dd5c4cSStephen Warren * Copyright (c) 2016, NVIDIA CORPORATION. 373dd5c4cSStephen Warren * 473dd5c4cSStephen Warren * SPDX-License-Identifier: GPL-2.0 573dd5c4cSStephen Warren */ 673dd5c4cSStephen Warren 773dd5c4cSStephen Warren #include <common.h> 873dd5c4cSStephen Warren #include <dm.h> 973dd5c4cSStephen Warren #include <dm/lists.h> 1073dd5c4cSStephen Warren #include <dm/root.h> 1173dd5c4cSStephen Warren #include <mailbox.h> 1273dd5c4cSStephen Warren #include <misc.h> 1373dd5c4cSStephen Warren #include <asm/arch-tegra/bpmp_abi.h> 1473dd5c4cSStephen Warren #include <asm/arch-tegra/ivc.h> 1573dd5c4cSStephen Warren 1673dd5c4cSStephen Warren #define BPMP_IVC_FRAME_COUNT 1 1773dd5c4cSStephen Warren #define BPMP_IVC_FRAME_SIZE 128 1873dd5c4cSStephen Warren 1973dd5c4cSStephen Warren #define BPMP_FLAG_DO_ACK BIT(0) 2073dd5c4cSStephen Warren #define BPMP_FLAG_RING_DOORBELL BIT(1) 2173dd5c4cSStephen Warren 2273dd5c4cSStephen Warren DECLARE_GLOBAL_DATA_PTR; 2373dd5c4cSStephen Warren 2473dd5c4cSStephen Warren struct tegra186_bpmp { 2573dd5c4cSStephen Warren struct mbox_chan mbox; 2673dd5c4cSStephen Warren struct tegra_ivc ivc; 2773dd5c4cSStephen Warren }; 2873dd5c4cSStephen Warren 2973dd5c4cSStephen Warren static int tegra186_bpmp_call(struct udevice *dev, int mrq, void *tx_msg, 3073dd5c4cSStephen Warren int tx_size, void *rx_msg, int rx_size) 3173dd5c4cSStephen Warren { 3273dd5c4cSStephen Warren struct tegra186_bpmp *priv = dev_get_priv(dev); 3373dd5c4cSStephen Warren int ret, err; 3473dd5c4cSStephen Warren void *ivc_frame; 3573dd5c4cSStephen Warren struct mrq_request *req; 3673dd5c4cSStephen Warren struct mrq_response *resp; 3773dd5c4cSStephen Warren ulong start_time; 3873dd5c4cSStephen Warren 3973dd5c4cSStephen Warren debug("%s(dev=%p, mrq=%u, tx_msg=%p, tx_size=%d, rx_msg=%p, rx_size=%d) (priv=%p)\n", 4073dd5c4cSStephen Warren __func__, dev, mrq, tx_msg, tx_size, rx_msg, rx_size, priv); 4173dd5c4cSStephen Warren 4273dd5c4cSStephen Warren if ((tx_size > BPMP_IVC_FRAME_SIZE) || (rx_size > BPMP_IVC_FRAME_SIZE)) 4373dd5c4cSStephen Warren return -EINVAL; 4473dd5c4cSStephen Warren 4573dd5c4cSStephen Warren ret = tegra_ivc_write_get_next_frame(&priv->ivc, &ivc_frame); 4673dd5c4cSStephen Warren if (ret) { 4773dd5c4cSStephen Warren error("tegra_ivc_write_get_next_frame() failed: %d\n", ret); 4873dd5c4cSStephen Warren return ret; 4973dd5c4cSStephen Warren } 5073dd5c4cSStephen Warren 5173dd5c4cSStephen Warren req = ivc_frame; 5273dd5c4cSStephen Warren req->mrq = mrq; 5373dd5c4cSStephen Warren req->flags = BPMP_FLAG_DO_ACK | BPMP_FLAG_RING_DOORBELL; 5473dd5c4cSStephen Warren memcpy(req + 1, tx_msg, tx_size); 5573dd5c4cSStephen Warren 5673dd5c4cSStephen Warren ret = tegra_ivc_write_advance(&priv->ivc); 5773dd5c4cSStephen Warren if (ret) { 5873dd5c4cSStephen Warren error("tegra_ivc_write_advance() failed: %d\n", ret); 5973dd5c4cSStephen Warren return ret; 6073dd5c4cSStephen Warren } 6173dd5c4cSStephen Warren 6273dd5c4cSStephen Warren start_time = timer_get_us(); 6373dd5c4cSStephen Warren for (;;) { 6473dd5c4cSStephen Warren ret = tegra_ivc_channel_notified(&priv->ivc); 6573dd5c4cSStephen Warren if (ret) { 6673dd5c4cSStephen Warren error("tegra_ivc_channel_notified() failed: %d\n", ret); 6773dd5c4cSStephen Warren return ret; 6873dd5c4cSStephen Warren } 6973dd5c4cSStephen Warren 7073dd5c4cSStephen Warren ret = tegra_ivc_read_get_next_frame(&priv->ivc, &ivc_frame); 7173dd5c4cSStephen Warren if (!ret) 7273dd5c4cSStephen Warren break; 7373dd5c4cSStephen Warren 7473dd5c4cSStephen Warren /* Timeout 20ms; roughly 10x current max observed duration */ 7573dd5c4cSStephen Warren if ((timer_get_us() - start_time) > 20 * 1000) { 7673dd5c4cSStephen Warren error("tegra_ivc_read_get_next_frame() timed out (%d)\n", 7773dd5c4cSStephen Warren ret); 7873dd5c4cSStephen Warren return -ETIMEDOUT; 7973dd5c4cSStephen Warren } 8073dd5c4cSStephen Warren } 8173dd5c4cSStephen Warren 8273dd5c4cSStephen Warren resp = ivc_frame; 8373dd5c4cSStephen Warren err = resp->err; 8473dd5c4cSStephen Warren if (!err && rx_msg && rx_size) 8573dd5c4cSStephen Warren memcpy(rx_msg, resp + 1, rx_size); 8673dd5c4cSStephen Warren 8773dd5c4cSStephen Warren ret = tegra_ivc_read_advance(&priv->ivc); 8873dd5c4cSStephen Warren if (ret) { 8973dd5c4cSStephen Warren error("tegra_ivc_write_advance() failed: %d\n", ret); 9073dd5c4cSStephen Warren return ret; 9173dd5c4cSStephen Warren } 9273dd5c4cSStephen Warren 9373dd5c4cSStephen Warren if (err) { 9473dd5c4cSStephen Warren error("BPMP responded with error %d\n", err); 9573dd5c4cSStephen Warren /* err isn't a U-Boot error code, so don't that */ 9673dd5c4cSStephen Warren return -EIO; 9773dd5c4cSStephen Warren } 9873dd5c4cSStephen Warren 9973dd5c4cSStephen Warren return rx_size; 10073dd5c4cSStephen Warren } 10173dd5c4cSStephen Warren 10273dd5c4cSStephen Warren /** 10373dd5c4cSStephen Warren * The BPMP exposes multiple different services. We create a sub-device for 10473dd5c4cSStephen Warren * each separate type of service, since each device must be of the appropriate 10573dd5c4cSStephen Warren * UCLASS. 10673dd5c4cSStephen Warren */ 10773dd5c4cSStephen Warren static int tegra186_bpmp_bind(struct udevice *dev) 10873dd5c4cSStephen Warren { 10973dd5c4cSStephen Warren int ret; 11073dd5c4cSStephen Warren struct udevice *child; 11173dd5c4cSStephen Warren 11273dd5c4cSStephen Warren debug("%s(dev=%p)\n", __func__, dev); 11373dd5c4cSStephen Warren 11473dd5c4cSStephen Warren ret = device_bind_driver_to_node(dev, "tegra186_clk", "tegra186_clk", 115*e160f7d4SSimon Glass dev_of_offset(dev), &child); 11673dd5c4cSStephen Warren if (ret) 11773dd5c4cSStephen Warren return ret; 11873dd5c4cSStephen Warren 11973dd5c4cSStephen Warren ret = device_bind_driver_to_node(dev, "tegra186_reset", 120*e160f7d4SSimon Glass "tegra186_reset", dev_of_offset(dev), 12173dd5c4cSStephen Warren &child); 12273dd5c4cSStephen Warren if (ret) 12373dd5c4cSStephen Warren return ret; 12473dd5c4cSStephen Warren 12573dd5c4cSStephen Warren ret = device_bind_driver_to_node(dev, "tegra186_power_domain", 12673dd5c4cSStephen Warren "tegra186_power_domain", 127*e160f7d4SSimon Glass dev_of_offset(dev), &child); 12873dd5c4cSStephen Warren if (ret) 12973dd5c4cSStephen Warren return ret; 13073dd5c4cSStephen Warren 13173dd5c4cSStephen Warren ret = dm_scan_fdt_dev(dev); 13273dd5c4cSStephen Warren if (ret) 13373dd5c4cSStephen Warren return ret; 13473dd5c4cSStephen Warren 13573dd5c4cSStephen Warren return 0; 13673dd5c4cSStephen Warren } 13773dd5c4cSStephen Warren 13873dd5c4cSStephen Warren static ulong tegra186_bpmp_get_shmem(struct udevice *dev, int index) 13973dd5c4cSStephen Warren { 14073dd5c4cSStephen Warren int ret; 14173dd5c4cSStephen Warren struct fdtdec_phandle_args args; 14273dd5c4cSStephen Warren fdt_addr_t reg; 14373dd5c4cSStephen Warren 144*e160f7d4SSimon Glass ret = fdtdec_parse_phandle_with_args(gd->fdt_blob, dev_of_offset(dev), 14573dd5c4cSStephen Warren "shmem", NULL, 0, index, &args); 14673dd5c4cSStephen Warren if (ret < 0) { 14773dd5c4cSStephen Warren error("fdtdec_parse_phandle_with_args() failed: %d\n", ret); 14873dd5c4cSStephen Warren return ret; 14973dd5c4cSStephen Warren } 15073dd5c4cSStephen Warren 15173dd5c4cSStephen Warren reg = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, args.node, 15273dd5c4cSStephen Warren "reg", 0, NULL, true); 15373dd5c4cSStephen Warren if (reg == FDT_ADDR_T_NONE) { 15473dd5c4cSStephen Warren error("fdtdec_get_addr_size_auto_noparent() failed\n"); 15573dd5c4cSStephen Warren return -ENODEV; 15673dd5c4cSStephen Warren } 15773dd5c4cSStephen Warren 15873dd5c4cSStephen Warren return reg; 15973dd5c4cSStephen Warren } 16073dd5c4cSStephen Warren 16173dd5c4cSStephen Warren static void tegra186_bpmp_ivc_notify(struct tegra_ivc *ivc) 16273dd5c4cSStephen Warren { 16373dd5c4cSStephen Warren struct tegra186_bpmp *priv = 16473dd5c4cSStephen Warren container_of(ivc, struct tegra186_bpmp, ivc); 16573dd5c4cSStephen Warren int ret; 16673dd5c4cSStephen Warren 16773dd5c4cSStephen Warren ret = mbox_send(&priv->mbox, NULL); 16873dd5c4cSStephen Warren if (ret) 16973dd5c4cSStephen Warren error("mbox_send() failed: %d\n", ret); 17073dd5c4cSStephen Warren } 17173dd5c4cSStephen Warren 17273dd5c4cSStephen Warren static int tegra186_bpmp_probe(struct udevice *dev) 17373dd5c4cSStephen Warren { 17473dd5c4cSStephen Warren struct tegra186_bpmp *priv = dev_get_priv(dev); 17573dd5c4cSStephen Warren int ret; 17673dd5c4cSStephen Warren ulong tx_base, rx_base, start_time; 17773dd5c4cSStephen Warren 17873dd5c4cSStephen Warren debug("%s(dev=%p) (priv=%p)\n", __func__, dev, priv); 17973dd5c4cSStephen Warren 18073dd5c4cSStephen Warren ret = mbox_get_by_index(dev, 0, &priv->mbox); 18173dd5c4cSStephen Warren if (ret) { 18273dd5c4cSStephen Warren error("mbox_get_by_index() failed: %d\n", ret); 18373dd5c4cSStephen Warren return ret; 18473dd5c4cSStephen Warren } 18573dd5c4cSStephen Warren 18673dd5c4cSStephen Warren tx_base = tegra186_bpmp_get_shmem(dev, 0); 18773dd5c4cSStephen Warren if (IS_ERR_VALUE(tx_base)) { 18873dd5c4cSStephen Warren error("tegra186_bpmp_get_shmem failed for tx_base\n"); 18973dd5c4cSStephen Warren return tx_base; 19073dd5c4cSStephen Warren } 19173dd5c4cSStephen Warren rx_base = tegra186_bpmp_get_shmem(dev, 1); 19273dd5c4cSStephen Warren if (IS_ERR_VALUE(rx_base)) { 19373dd5c4cSStephen Warren error("tegra186_bpmp_get_shmem failed for rx_base\n"); 19473dd5c4cSStephen Warren return rx_base; 19573dd5c4cSStephen Warren } 19673dd5c4cSStephen Warren debug("shmem: rx=%lx, tx=%lx\n", rx_base, tx_base); 19773dd5c4cSStephen Warren 19873dd5c4cSStephen Warren ret = tegra_ivc_init(&priv->ivc, rx_base, tx_base, BPMP_IVC_FRAME_COUNT, 19973dd5c4cSStephen Warren BPMP_IVC_FRAME_SIZE, tegra186_bpmp_ivc_notify); 20073dd5c4cSStephen Warren if (ret) { 20173dd5c4cSStephen Warren error("tegra_ivc_init() failed: %d\n", ret); 20273dd5c4cSStephen Warren return ret; 20373dd5c4cSStephen Warren } 20473dd5c4cSStephen Warren 20573dd5c4cSStephen Warren tegra_ivc_channel_reset(&priv->ivc); 20673dd5c4cSStephen Warren start_time = timer_get_us(); 20773dd5c4cSStephen Warren for (;;) { 20873dd5c4cSStephen Warren ret = tegra_ivc_channel_notified(&priv->ivc); 20973dd5c4cSStephen Warren if (!ret) 21073dd5c4cSStephen Warren break; 21173dd5c4cSStephen Warren 21273dd5c4cSStephen Warren /* Timeout 100ms */ 21373dd5c4cSStephen Warren if ((timer_get_us() - start_time) > 100 * 1000) { 21473dd5c4cSStephen Warren error("Initial IVC reset timed out (%d)\n", ret); 21573dd5c4cSStephen Warren ret = -ETIMEDOUT; 21673dd5c4cSStephen Warren goto err_free_mbox; 21773dd5c4cSStephen Warren } 21873dd5c4cSStephen Warren } 21973dd5c4cSStephen Warren 22073dd5c4cSStephen Warren return 0; 22173dd5c4cSStephen Warren 22273dd5c4cSStephen Warren err_free_mbox: 22373dd5c4cSStephen Warren mbox_free(&priv->mbox); 22473dd5c4cSStephen Warren 22573dd5c4cSStephen Warren return ret; 22673dd5c4cSStephen Warren } 22773dd5c4cSStephen Warren 22873dd5c4cSStephen Warren static int tegra186_bpmp_remove(struct udevice *dev) 22973dd5c4cSStephen Warren { 23073dd5c4cSStephen Warren struct tegra186_bpmp *priv = dev_get_priv(dev); 23173dd5c4cSStephen Warren 23273dd5c4cSStephen Warren debug("%s(dev=%p) (priv=%p)\n", __func__, dev, priv); 23373dd5c4cSStephen Warren 23473dd5c4cSStephen Warren mbox_free(&priv->mbox); 23573dd5c4cSStephen Warren 23673dd5c4cSStephen Warren return 0; 23773dd5c4cSStephen Warren } 23873dd5c4cSStephen Warren 23973dd5c4cSStephen Warren static struct misc_ops tegra186_bpmp_ops = { 24073dd5c4cSStephen Warren .call = tegra186_bpmp_call, 24173dd5c4cSStephen Warren }; 24273dd5c4cSStephen Warren 24373dd5c4cSStephen Warren static const struct udevice_id tegra186_bpmp_ids[] = { 24473dd5c4cSStephen Warren { .compatible = "nvidia,tegra186-bpmp" }, 24573dd5c4cSStephen Warren { } 24673dd5c4cSStephen Warren }; 24773dd5c4cSStephen Warren 24873dd5c4cSStephen Warren U_BOOT_DRIVER(tegra186_bpmp) = { 24973dd5c4cSStephen Warren .name = "tegra186_bpmp", 25073dd5c4cSStephen Warren .id = UCLASS_MISC, 25173dd5c4cSStephen Warren .of_match = tegra186_bpmp_ids, 25273dd5c4cSStephen Warren .bind = tegra186_bpmp_bind, 25373dd5c4cSStephen Warren .probe = tegra186_bpmp_probe, 25473dd5c4cSStephen Warren .remove = tegra186_bpmp_remove, 25573dd5c4cSStephen Warren .ops = &tegra186_bpmp_ops, 25673dd5c4cSStephen Warren .priv_auto_alloc_size = sizeof(struct tegra186_bpmp), 25773dd5c4cSStephen Warren }; 258