blob: d7e6d69f978ef0e95901f08860927f1779aeac21 [file] [log] [blame]
/*
* Copyright (c) 2016, NVIDIA CORPORATION.
* Copright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(DATA61_GPL)
*/
/*
* This is a port of the Tegra186 BPMP sources from U-boot with some additional
* modifications. Similar to the Tegra IVC protocol, there's no documentation
* on the BPMP module ABI.
*/
#include <string.h>
#include <platsupport/pmem.h>
#include <platsupport/fdt.h>
#include <platsupport/driver_module.h>
#include <tx2bpmp/bpmp.h>
#include <tx2bpmp/hsp.h>
#include <tx2bpmp/ivc.h>
#include <utils/util.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)
#define TX_SHMEM 0
#define RX_SHMEM 1
#define NUM_SHMEM 2
#define TIMEOUT_THRESHOLD 2000000ul
struct tx2_bpmp_priv {
ps_io_ops_t *io_ops;
tx2_hsp_t hsp;
bool hsp_initialised;
struct tegra_ivc ivc;
void *tx_base; // Virtual address base of the TX shared memory channel
void *rx_base; // Virtual address base of the RX shared memory channel
pmem_region_t bpmp_shmems[NUM_SHMEM];
};
static bool bpmp_initialised = false;
static unsigned int bpmp_refcount = 0;
static struct tx2_bpmp_priv bpmp_data = {0};
static int bpmp_call(void *data, int mrq, void *tx_msg, size_t tx_size, void *rx_msg, size_t rx_size)
{
int ret, err;
void *ivc_frame;
struct mrq_request *req;
struct mrq_response *resp;
unsigned long timeout = TIMEOUT_THRESHOLD;
struct tx2_bpmp_priv *bpmp_priv = data;
if ((tx_size > BPMP_IVC_FRAME_SIZE) || (rx_size > BPMP_IVC_FRAME_SIZE))
return -EINVAL;
ret = tegra_ivc_write_get_next_frame(&bpmp_priv->ivc, &ivc_frame);
if (ret) {
ZF_LOGE("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(&bpmp_priv->ivc);
if (ret) {
ZF_LOGE("tegra_ivc_write_advance() failed: %d\n", ret);
return ret;
}
for (; timeout > 0; timeout--) {
ret = tegra_ivc_channel_notified(&bpmp_priv->ivc);
if (ret) {
ZF_LOGE("tegra_ivc_channel_notified() failed: %d\n", ret);
return ret;
}
ret = tegra_ivc_read_get_next_frame(&bpmp_priv->ivc, &ivc_frame);
if (!ret)
break;
}
if (!timeout) {
ZF_LOGE("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(&bpmp_priv->ivc);
if (ret) {
ZF_LOGE("tegra_ivc_write_advance() failed: %d\n", ret);
return ret;
}
if (err) {
ZF_LOGE("BPMP responded with error %d\n", err);
/* err isn't a U-Boot error code, so don't that */
return -EIO;
}
return rx_size;
}
static void bpmp_ivc_notify(struct tegra_ivc *ivc, void *token)
{
struct tx2_bpmp_priv *bpmp_priv = token;
int ret;
ret = tx2_hsp_doorbell_ring(&bpmp_priv->hsp, BPMP_DBELL);
if (ret)
ZF_LOGF("Failed to ring BPMP's doorbell in the HSP: %d\n", ret);
}
static int bpmp_destroy(void *data)
{
struct tx2_bpmp_priv *bpmp_priv = data;
bpmp_refcount--;
if (bpmp_refcount != 0) {
/* Only cleanup the BPMP structure if there are no more references that are valid. */
return 0;
}
if (bpmp_priv->hsp_initialised) {
ZF_LOGF_IF(tx2_hsp_destroy(&bpmp_priv->hsp),
"Failed to clean up after a failed BPMP initialisation process!");
}
/* Unmapping the shared memory also destroys the IVC */
if (bpmp_priv->tx_base) {
ps_io_unmap(&bpmp_priv->io_ops->io_mapper, bpmp_priv->tx_base, bpmp_data.bpmp_shmems[TX_SHMEM].length);
}
if (bpmp_priv->rx_base) {
ps_io_unmap(&bpmp_priv->io_ops->io_mapper, bpmp_priv->rx_base, bpmp_data.bpmp_shmems[RX_SHMEM].length);
}
return 0;
}
static int allocate_register_callback(pmem_region_t pmem, unsigned curr_num, size_t num_regs, void *token)
{
if (curr_num == 1) {
bpmp_data.tx_base = ps_pmem_map(bpmp_data.io_ops, pmem, false, PS_MEM_NORMAL);
if (!bpmp_data.tx_base) {
ZF_LOGE("Failed to map the TX BPMP channel");
return -EIO;
}
bpmp_data.bpmp_shmems[TX_SHMEM] = pmem;
} else if (curr_num == 2) {
bpmp_data.rx_base = ps_pmem_map(bpmp_data.io_ops, pmem, false, PS_MEM_NORMAL);
if (!bpmp_data.rx_base) {
ZF_LOGE("Failed to map the RX BPMP channel");
return -EIO;
}
bpmp_data.bpmp_shmems[RX_SHMEM] = pmem;
}
return 0;
}
int tx2_bpmp_init(ps_io_ops_t *io_ops, struct tx2_bpmp *bpmp)
{
if (!io_ops || !bpmp) {
ZF_LOGE("Arguments are NULL!");
return -EINVAL;
}
if (bpmp_initialised) {
/* If we've initialised the BPMP once, just fill the private data with
* what we've initialised */
goto success;
}
int ret = 0;
/* Not sure if this is too long or too short. */
unsigned long timeout = TIMEOUT_THRESHOLD;
ret = tx2_hsp_init(io_ops, &bpmp_data.hsp, "/tegra-hsp@3c00000");
if (ret) {
ZF_LOGE("Failed to initialise the HSP device for BPMP");
return ret;
}
bpmp_data.io_ops = io_ops;
bpmp_data.hsp_initialised = true;
ps_fdt_cookie_t *cookie = NULL;
ret = ps_fdt_read_path(&io_ops->io_fdt, &io_ops->malloc_ops, "/bpmp", &cookie);
if (ret) {
ZF_LOGE("Failed to find %s in device tree", "/bpmp");
return -ENODEV;
}
/* walk the registers and allocate them */
ret = ps_fdt_walk_registers(&io_ops->io_fdt, cookie, allocate_register_callback, NULL);
if (ret) {
ZF_LOGE("Failed to walk fdt node");
return -ENODEV;
}
ret = ps_fdt_cleanup_cookie(&io_ops->malloc_ops, cookie);
if (ret) {
return -ENODEV;
}
ret = tegra_ivc_init(&bpmp_data.ivc, (unsigned long) bpmp_data.rx_base, (unsigned long) bpmp_data.tx_base,
BPMP_IVC_FRAME_COUNT, BPMP_IVC_FRAME_SIZE, bpmp_ivc_notify, (void *) &bpmp_data);
if (ret) {
ZF_LOGE("tegra_ivc_init() failed: %d", ret);
goto fail;
}
tegra_ivc_channel_reset(&bpmp_data.ivc);
for (; timeout > 0; timeout--) {
ret = tegra_ivc_channel_notified(&bpmp_data.ivc);
if (!ret) {
break;
}
}
if (!timeout) {
ZF_LOGE("Initial IVC reset timed out (%d)", ret);
ret = -ETIMEDOUT;
goto fail;
}
ret = ps_interface_register(&io_ops->interface_registration_ops, TX2_BPMP_INTERFACE,
bpmp, NULL);
if (ret) {
ZF_LOGE("Failed to register the BPMP interface");
goto fail;
}
success:
bpmp_refcount++;
bpmp->data = &bpmp_data;
bpmp->call = bpmp_call;
bpmp->destroy = bpmp_destroy;
bpmp_initialised = true;
/* Register this BPMP interface so that the reset driver can access it */
return 0;
fail:
ZF_LOGF_IF(bpmp_destroy(&bpmp_data), "Failed to cleanup the BPMP after a failed initialisation");
return ret;
}
int tx2_bpmp_init_module(ps_io_ops_t *io_ops, const char *device_path) {
struct tx2_bpmp *bpmp;
int error = ps_calloc(&io_ops->malloc_ops, 1, sizeof(*bpmp), (void **)&bpmp);
if (error) {
ZF_LOGE("Failed to allocate struct for tx2_bpmp");
return -1;
}
error = tx2_bpmp_init(io_ops, bpmp);
if (error) {
ZF_LOGE("Failed to initialize bpmp driver");
return -1;
}
return 0;
}
static const char*compatible_strings[] = {
"nvidia,tegra186-bpmp",
NULL
};
PS_DRIVER_MODULE_DEFINE(bpmp, compatible_strings, tx2_bpmp_init_module);