mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
7.2 KiB
305 lines
7.2 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (c) 2018, NVIDIA CORPORATION. |
|
*/ |
|
|
|
#include <linux/genalloc.h> |
|
#include <linux/mailbox_client.h> |
|
#include <linux/platform_device.h> |
|
|
|
#include <soc/tegra/bpmp.h> |
|
#include <soc/tegra/bpmp-abi.h> |
|
#include <soc/tegra/ivc.h> |
|
|
|
#include "bpmp-private.h" |
|
|
|
struct tegra186_bpmp { |
|
struct tegra_bpmp *parent; |
|
|
|
struct { |
|
struct gen_pool *pool; |
|
dma_addr_t phys; |
|
void *virt; |
|
} tx, rx; |
|
|
|
struct { |
|
struct mbox_client client; |
|
struct mbox_chan *channel; |
|
} mbox; |
|
}; |
|
|
|
static inline struct tegra_bpmp * |
|
mbox_client_to_bpmp(struct mbox_client *client) |
|
{ |
|
struct tegra186_bpmp *priv; |
|
|
|
priv = container_of(client, struct tegra186_bpmp, mbox.client); |
|
|
|
return priv->parent; |
|
} |
|
|
|
static bool tegra186_bpmp_is_message_ready(struct tegra_bpmp_channel *channel) |
|
{ |
|
void *frame; |
|
|
|
frame = tegra_ivc_read_get_next_frame(channel->ivc); |
|
if (IS_ERR(frame)) { |
|
channel->ib = NULL; |
|
return false; |
|
} |
|
|
|
channel->ib = frame; |
|
|
|
return true; |
|
} |
|
|
|
static bool tegra186_bpmp_is_channel_free(struct tegra_bpmp_channel *channel) |
|
{ |
|
void *frame; |
|
|
|
frame = tegra_ivc_write_get_next_frame(channel->ivc); |
|
if (IS_ERR(frame)) { |
|
channel->ob = NULL; |
|
return false; |
|
} |
|
|
|
channel->ob = frame; |
|
|
|
return true; |
|
} |
|
|
|
static int tegra186_bpmp_ack_message(struct tegra_bpmp_channel *channel) |
|
{ |
|
return tegra_ivc_read_advance(channel->ivc); |
|
} |
|
|
|
static int tegra186_bpmp_post_message(struct tegra_bpmp_channel *channel) |
|
{ |
|
return tegra_ivc_write_advance(channel->ivc); |
|
} |
|
|
|
static int tegra186_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) |
|
{ |
|
struct tegra186_bpmp *priv = bpmp->priv; |
|
int err; |
|
|
|
err = mbox_send_message(priv->mbox.channel, NULL); |
|
if (err < 0) |
|
return err; |
|
|
|
mbox_client_txdone(priv->mbox.channel, 0); |
|
|
|
return 0; |
|
} |
|
|
|
static void tegra186_bpmp_ivc_notify(struct tegra_ivc *ivc, void *data) |
|
{ |
|
struct tegra_bpmp *bpmp = data; |
|
struct tegra186_bpmp *priv = bpmp->priv; |
|
|
|
if (WARN_ON(priv->mbox.channel == NULL)) |
|
return; |
|
|
|
tegra186_bpmp_ring_doorbell(bpmp); |
|
} |
|
|
|
static int tegra186_bpmp_channel_init(struct tegra_bpmp_channel *channel, |
|
struct tegra_bpmp *bpmp, |
|
unsigned int index) |
|
{ |
|
struct tegra186_bpmp *priv = bpmp->priv; |
|
size_t message_size, queue_size; |
|
unsigned int offset; |
|
int err; |
|
|
|
channel->ivc = devm_kzalloc(bpmp->dev, sizeof(*channel->ivc), |
|
GFP_KERNEL); |
|
if (!channel->ivc) |
|
return -ENOMEM; |
|
|
|
message_size = tegra_ivc_align(MSG_MIN_SZ); |
|
queue_size = tegra_ivc_total_queue_size(message_size); |
|
offset = queue_size * index; |
|
|
|
err = tegra_ivc_init(channel->ivc, NULL, |
|
priv->rx.virt + offset, priv->rx.phys + offset, |
|
priv->tx.virt + offset, priv->tx.phys + offset, |
|
1, message_size, tegra186_bpmp_ivc_notify, |
|
bpmp); |
|
if (err < 0) { |
|
dev_err(bpmp->dev, "failed to setup IVC for channel %u: %d\n", |
|
index, err); |
|
return err; |
|
} |
|
|
|
init_completion(&channel->completion); |
|
channel->bpmp = bpmp; |
|
|
|
return 0; |
|
} |
|
|
|
static void tegra186_bpmp_channel_reset(struct tegra_bpmp_channel *channel) |
|
{ |
|
/* reset the channel state */ |
|
tegra_ivc_reset(channel->ivc); |
|
|
|
/* sync the channel state with BPMP */ |
|
while (tegra_ivc_notified(channel->ivc)) |
|
; |
|
} |
|
|
|
static void tegra186_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel) |
|
{ |
|
tegra_ivc_cleanup(channel->ivc); |
|
} |
|
|
|
static void mbox_handle_rx(struct mbox_client *client, void *data) |
|
{ |
|
struct tegra_bpmp *bpmp = mbox_client_to_bpmp(client); |
|
|
|
tegra_bpmp_handle_rx(bpmp); |
|
} |
|
|
|
static int tegra186_bpmp_init(struct tegra_bpmp *bpmp) |
|
{ |
|
struct tegra186_bpmp *priv; |
|
unsigned int i; |
|
int err; |
|
|
|
priv = devm_kzalloc(bpmp->dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
bpmp->priv = priv; |
|
priv->parent = bpmp; |
|
|
|
priv->tx.pool = of_gen_pool_get(bpmp->dev->of_node, "shmem", 0); |
|
if (!priv->tx.pool) { |
|
dev_err(bpmp->dev, "TX shmem pool not found\n"); |
|
return -EPROBE_DEFER; |
|
} |
|
|
|
priv->tx.virt = gen_pool_dma_alloc(priv->tx.pool, 4096, &priv->tx.phys); |
|
if (!priv->tx.virt) { |
|
dev_err(bpmp->dev, "failed to allocate from TX pool\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
priv->rx.pool = of_gen_pool_get(bpmp->dev->of_node, "shmem", 1); |
|
if (!priv->rx.pool) { |
|
dev_err(bpmp->dev, "RX shmem pool not found\n"); |
|
err = -EPROBE_DEFER; |
|
goto free_tx; |
|
} |
|
|
|
priv->rx.virt = gen_pool_dma_alloc(priv->rx.pool, 4096, &priv->rx.phys); |
|
if (!priv->rx.virt) { |
|
dev_err(bpmp->dev, "failed to allocate from RX pool\n"); |
|
err = -ENOMEM; |
|
goto free_tx; |
|
} |
|
|
|
err = tegra186_bpmp_channel_init(bpmp->tx_channel, bpmp, |
|
bpmp->soc->channels.cpu_tx.offset); |
|
if (err < 0) |
|
goto free_rx; |
|
|
|
err = tegra186_bpmp_channel_init(bpmp->rx_channel, bpmp, |
|
bpmp->soc->channels.cpu_rx.offset); |
|
if (err < 0) |
|
goto cleanup_tx_channel; |
|
|
|
for (i = 0; i < bpmp->threaded.count; i++) { |
|
unsigned int index = bpmp->soc->channels.thread.offset + i; |
|
|
|
err = tegra186_bpmp_channel_init(&bpmp->threaded_channels[i], |
|
bpmp, index); |
|
if (err < 0) |
|
goto cleanup_channels; |
|
} |
|
|
|
/* mbox registration */ |
|
priv->mbox.client.dev = bpmp->dev; |
|
priv->mbox.client.rx_callback = mbox_handle_rx; |
|
priv->mbox.client.tx_block = false; |
|
priv->mbox.client.knows_txdone = false; |
|
|
|
priv->mbox.channel = mbox_request_channel(&priv->mbox.client, 0); |
|
if (IS_ERR(priv->mbox.channel)) { |
|
err = PTR_ERR(priv->mbox.channel); |
|
dev_err(bpmp->dev, "failed to get HSP mailbox: %d\n", err); |
|
goto cleanup_channels; |
|
} |
|
|
|
tegra186_bpmp_channel_reset(bpmp->tx_channel); |
|
tegra186_bpmp_channel_reset(bpmp->rx_channel); |
|
|
|
for (i = 0; i < bpmp->threaded.count; i++) |
|
tegra186_bpmp_channel_reset(&bpmp->threaded_channels[i]); |
|
|
|
return 0; |
|
|
|
cleanup_channels: |
|
for (i = 0; i < bpmp->threaded.count; i++) { |
|
if (!bpmp->threaded_channels[i].bpmp) |
|
continue; |
|
|
|
tegra186_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); |
|
} |
|
|
|
tegra186_bpmp_channel_cleanup(bpmp->rx_channel); |
|
cleanup_tx_channel: |
|
tegra186_bpmp_channel_cleanup(bpmp->tx_channel); |
|
free_rx: |
|
gen_pool_free(priv->rx.pool, (unsigned long)priv->rx.virt, 4096); |
|
free_tx: |
|
gen_pool_free(priv->tx.pool, (unsigned long)priv->tx.virt, 4096); |
|
|
|
return err; |
|
} |
|
|
|
static void tegra186_bpmp_deinit(struct tegra_bpmp *bpmp) |
|
{ |
|
struct tegra186_bpmp *priv = bpmp->priv; |
|
unsigned int i; |
|
|
|
mbox_free_channel(priv->mbox.channel); |
|
|
|
for (i = 0; i < bpmp->threaded.count; i++) |
|
tegra186_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); |
|
|
|
tegra186_bpmp_channel_cleanup(bpmp->rx_channel); |
|
tegra186_bpmp_channel_cleanup(bpmp->tx_channel); |
|
|
|
gen_pool_free(priv->rx.pool, (unsigned long)priv->rx.virt, 4096); |
|
gen_pool_free(priv->tx.pool, (unsigned long)priv->tx.virt, 4096); |
|
} |
|
|
|
static int tegra186_bpmp_resume(struct tegra_bpmp *bpmp) |
|
{ |
|
unsigned int i; |
|
|
|
/* reset message channels */ |
|
tegra186_bpmp_channel_reset(bpmp->tx_channel); |
|
tegra186_bpmp_channel_reset(bpmp->rx_channel); |
|
|
|
for (i = 0; i < bpmp->threaded.count; i++) |
|
tegra186_bpmp_channel_reset(&bpmp->threaded_channels[i]); |
|
|
|
return 0; |
|
} |
|
|
|
const struct tegra_bpmp_ops tegra186_bpmp_ops = { |
|
.init = tegra186_bpmp_init, |
|
.deinit = tegra186_bpmp_deinit, |
|
.is_response_ready = tegra186_bpmp_is_message_ready, |
|
.is_request_ready = tegra186_bpmp_is_message_ready, |
|
.ack_response = tegra186_bpmp_ack_message, |
|
.ack_request = tegra186_bpmp_ack_message, |
|
.is_response_channel_free = tegra186_bpmp_is_channel_free, |
|
.is_request_channel_free = tegra186_bpmp_is_channel_free, |
|
.post_response = tegra186_bpmp_post_message, |
|
.post_request = tegra186_bpmp_post_message, |
|
.ring_doorbell = tegra186_bpmp_ring_doorbell, |
|
.resume = tegra186_bpmp_resume, |
|
};
|
|
|