mirror of
https://github.com/torvalds/linux.git
synced 2024-11-13 07:31:45 +00:00
firmware: tegra: Add BPMP support
The Boot and Power Management Processor (BPMP) is a co-processor found on Tegra SoCs. It is designed to handle the early stages of the boot process and offload power management tasks (such as clocks, resets, powergates, ...) as well as system control services. Compared to the ARM SCPI, the services provided by BPMP are message- based rather than method-based. The BPMP firmware driver provides the services to transmit data to and receive data from the BPMP. Users can also register a Message ReQuest (MRQ), for which a service routine will be run when a corresponding event is received from the firmware. A set of messages, called the BPMP ABI, are specified for a number of different services provided by the BPMP (such as clocks or resets). Based on work by Sivaram Nair <sivaramn@nvidia.com> and Joseph Lo <josephl@nvidia.com>. Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
ca791d7f42
commit
983de5f971
@ -10,4 +10,16 @@ config TEGRA_IVC
|
||||
keeps the content is synchronization between host CPU and remote
|
||||
processors.
|
||||
|
||||
config TEGRA_BPMP
|
||||
bool "Tegra BPMP driver"
|
||||
depends on ARCH_TEGRA && TEGRA_HSP_MBOX && TEGRA_IVC
|
||||
help
|
||||
BPMP (Boot and Power Management Processor) is designed to off-loading
|
||||
the PM functions which include clock/DVFS/thermal/power from the CPU.
|
||||
It needs HSP as the HW synchronization and notification module and
|
||||
IVC module as the message communication protocol.
|
||||
|
||||
This driver manages the IPC interface between host CPU and the
|
||||
firmware running on BPMP.
|
||||
|
||||
endmenu
|
||||
|
@ -1 +1,2 @@
|
||||
obj-$(CONFIG_TEGRA_BPMP) += bpmp.o
|
||||
obj-$(CONFIG_TEGRA_IVC) += ivc.o
|
||||
|
868
drivers/firmware/tegra/bpmp.c
Normal file
868
drivers/firmware/tegra/bpmp.c
Normal file
@ -0,0 +1,868 @@
|
||||
/*
|
||||
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk/tegra.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/mailbox_client.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/semaphore.h>
|
||||
|
||||
#include <soc/tegra/bpmp.h>
|
||||
#include <soc/tegra/bpmp-abi.h>
|
||||
#include <soc/tegra/ivc.h>
|
||||
|
||||
#define MSG_ACK BIT(0)
|
||||
#define MSG_RING BIT(1)
|
||||
|
||||
static inline struct tegra_bpmp *
|
||||
mbox_client_to_bpmp(struct mbox_client *client)
|
||||
{
|
||||
return container_of(client, struct tegra_bpmp, mbox.client);
|
||||
}
|
||||
|
||||
struct tegra_bpmp *tegra_bpmp_get(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct tegra_bpmp *bpmp;
|
||||
struct device_node *np;
|
||||
|
||||
np = of_parse_phandle(dev->of_node, "nvidia,bpmp", 0);
|
||||
if (!np)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev) {
|
||||
bpmp = ERR_PTR(-ENODEV);
|
||||
goto put;
|
||||
}
|
||||
|
||||
bpmp = platform_get_drvdata(pdev);
|
||||
if (!bpmp) {
|
||||
bpmp = ERR_PTR(-EPROBE_DEFER);
|
||||
put_device(&pdev->dev);
|
||||
goto put;
|
||||
}
|
||||
|
||||
put:
|
||||
of_node_put(np);
|
||||
return bpmp;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_bpmp_get);
|
||||
|
||||
void tegra_bpmp_put(struct tegra_bpmp *bpmp)
|
||||
{
|
||||
if (bpmp)
|
||||
put_device(bpmp->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_bpmp_put);
|
||||
|
||||
static int tegra_bpmp_channel_get_index(struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
return channel - channel->bpmp->channels;
|
||||
}
|
||||
|
||||
static int
|
||||
tegra_bpmp_channel_get_thread_index(struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
struct tegra_bpmp *bpmp = channel->bpmp;
|
||||
unsigned int offset, count;
|
||||
int index;
|
||||
|
||||
offset = bpmp->soc->channels.thread.offset;
|
||||
count = bpmp->soc->channels.thread.count;
|
||||
|
||||
index = tegra_bpmp_channel_get_index(channel);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
if (index < offset || index >= offset + count)
|
||||
return -EINVAL;
|
||||
|
||||
return index - offset;
|
||||
}
|
||||
|
||||
static struct tegra_bpmp_channel *
|
||||
tegra_bpmp_channel_get_thread(struct tegra_bpmp *bpmp, unsigned int index)
|
||||
{
|
||||
unsigned int offset = bpmp->soc->channels.thread.offset;
|
||||
unsigned int count = bpmp->soc->channels.thread.count;
|
||||
|
||||
if (index >= count)
|
||||
return NULL;
|
||||
|
||||
return &bpmp->channels[offset + index];
|
||||
}
|
||||
|
||||
static struct tegra_bpmp_channel *
|
||||
tegra_bpmp_channel_get_tx(struct tegra_bpmp *bpmp)
|
||||
{
|
||||
unsigned int offset = bpmp->soc->channels.cpu_tx.offset;
|
||||
|
||||
return &bpmp->channels[offset + smp_processor_id()];
|
||||
}
|
||||
|
||||
static struct tegra_bpmp_channel *
|
||||
tegra_bpmp_channel_get_rx(struct tegra_bpmp *bpmp)
|
||||
{
|
||||
unsigned int offset = bpmp->soc->channels.cpu_rx.offset;
|
||||
|
||||
return &bpmp->channels[offset];
|
||||
}
|
||||
|
||||
static bool tegra_bpmp_message_valid(const struct tegra_bpmp_message *msg)
|
||||
{
|
||||
return (msg->tx.size <= MSG_DATA_MIN_SZ) &&
|
||||
(msg->rx.size <= MSG_DATA_MIN_SZ) &&
|
||||
(msg->tx.size == 0 || msg->tx.data) &&
|
||||
(msg->rx.size == 0 || msg->rx.data);
|
||||
}
|
||||
|
||||
static bool tegra_bpmp_master_acked(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 int tegra_bpmp_wait_ack(struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
unsigned long timeout = channel->bpmp->soc->channels.cpu_tx.timeout;
|
||||
ktime_t end;
|
||||
|
||||
end = ktime_add_us(ktime_get(), timeout);
|
||||
|
||||
do {
|
||||
if (tegra_bpmp_master_acked(channel))
|
||||
return 0;
|
||||
} while (ktime_before(ktime_get(), end));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static bool tegra_bpmp_master_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 tegra_bpmp_wait_master_free(struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
unsigned long timeout = channel->bpmp->soc->channels.cpu_tx.timeout;
|
||||
ktime_t start, now;
|
||||
|
||||
start = ns_to_ktime(local_clock());
|
||||
|
||||
do {
|
||||
if (tegra_bpmp_master_free(channel))
|
||||
return 0;
|
||||
|
||||
now = ns_to_ktime(local_clock());
|
||||
} while (ktime_us_delta(now, start) < timeout);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static ssize_t __tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel,
|
||||
void *data, size_t size)
|
||||
{
|
||||
if (data && size > 0)
|
||||
memcpy(data, channel->ib->data, size);
|
||||
|
||||
return tegra_ivc_read_advance(channel->ivc);
|
||||
}
|
||||
|
||||
static ssize_t tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel,
|
||||
void *data, size_t size)
|
||||
{
|
||||
struct tegra_bpmp *bpmp = channel->bpmp;
|
||||
unsigned long flags;
|
||||
ssize_t err;
|
||||
int index;
|
||||
|
||||
index = tegra_bpmp_channel_get_thread_index(channel);
|
||||
if (index < 0)
|
||||
return index;
|
||||
|
||||
spin_lock_irqsave(&bpmp->lock, flags);
|
||||
err = __tegra_bpmp_channel_read(channel, data, size);
|
||||
clear_bit(index, bpmp->threaded.allocated);
|
||||
spin_unlock_irqrestore(&bpmp->lock, flags);
|
||||
|
||||
up(&bpmp->threaded.lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t __tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel,
|
||||
unsigned int mrq, unsigned long flags,
|
||||
const void *data, size_t size)
|
||||
{
|
||||
channel->ob->code = mrq;
|
||||
channel->ob->flags = flags;
|
||||
|
||||
if (data && size > 0)
|
||||
memcpy(channel->ob->data, data, size);
|
||||
|
||||
return tegra_ivc_write_advance(channel->ivc);
|
||||
}
|
||||
|
||||
static struct tegra_bpmp_channel *
|
||||
tegra_bpmp_write_threaded(struct tegra_bpmp *bpmp, unsigned int mrq,
|
||||
const void *data, size_t size)
|
||||
{
|
||||
unsigned long timeout = bpmp->soc->channels.thread.timeout;
|
||||
unsigned int count = bpmp->soc->channels.thread.count;
|
||||
struct tegra_bpmp_channel *channel;
|
||||
unsigned long flags;
|
||||
unsigned int index;
|
||||
int err;
|
||||
|
||||
err = down_timeout(&bpmp->threaded.lock, usecs_to_jiffies(timeout));
|
||||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
|
||||
spin_lock_irqsave(&bpmp->lock, flags);
|
||||
|
||||
index = find_first_zero_bit(bpmp->threaded.allocated, count);
|
||||
if (index == count) {
|
||||
channel = ERR_PTR(-EBUSY);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
channel = tegra_bpmp_channel_get_thread(bpmp, index);
|
||||
if (!channel) {
|
||||
channel = ERR_PTR(-EINVAL);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (!tegra_bpmp_master_free(channel)) {
|
||||
channel = ERR_PTR(-EBUSY);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
set_bit(index, bpmp->threaded.allocated);
|
||||
|
||||
err = __tegra_bpmp_channel_write(channel, mrq, MSG_ACK | MSG_RING,
|
||||
data, size);
|
||||
if (err < 0) {
|
||||
clear_bit(index, bpmp->threaded.allocated);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
set_bit(index, bpmp->threaded.busy);
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&bpmp->lock, flags);
|
||||
return channel;
|
||||
}
|
||||
|
||||
static ssize_t tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel,
|
||||
unsigned int mrq, unsigned long flags,
|
||||
const void *data, size_t size)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = tegra_bpmp_wait_master_free(channel);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return __tegra_bpmp_channel_write(channel, mrq, flags, data, size);
|
||||
}
|
||||
|
||||
int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
|
||||
struct tegra_bpmp_message *msg)
|
||||
{
|
||||
struct tegra_bpmp_channel *channel;
|
||||
int err;
|
||||
|
||||
if (WARN_ON(!irqs_disabled()))
|
||||
return -EPERM;
|
||||
|
||||
if (!tegra_bpmp_message_valid(msg))
|
||||
return -EINVAL;
|
||||
|
||||
channel = tegra_bpmp_channel_get_tx(bpmp);
|
||||
|
||||
err = tegra_bpmp_channel_write(channel, msg->mrq, MSG_ACK,
|
||||
msg->tx.data, msg->tx.size);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = mbox_send_message(bpmp->mbox.channel, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mbox_client_txdone(bpmp->mbox.channel, 0);
|
||||
|
||||
err = tegra_bpmp_wait_ack(channel);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return __tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_bpmp_transfer_atomic);
|
||||
|
||||
int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
|
||||
struct tegra_bpmp_message *msg)
|
||||
{
|
||||
struct tegra_bpmp_channel *channel;
|
||||
unsigned long timeout;
|
||||
int err;
|
||||
|
||||
if (WARN_ON(irqs_disabled()))
|
||||
return -EPERM;
|
||||
|
||||
if (!tegra_bpmp_message_valid(msg))
|
||||
return -EINVAL;
|
||||
|
||||
channel = tegra_bpmp_write_threaded(bpmp, msg->mrq, msg->tx.data,
|
||||
msg->tx.size);
|
||||
if (IS_ERR(channel))
|
||||
return PTR_ERR(channel);
|
||||
|
||||
err = mbox_send_message(bpmp->mbox.channel, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mbox_client_txdone(bpmp->mbox.channel, 0);
|
||||
|
||||
timeout = usecs_to_jiffies(bpmp->soc->channels.thread.timeout);
|
||||
|
||||
err = wait_for_completion_timeout(&channel->completion, timeout);
|
||||
if (err == 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_bpmp_transfer);
|
||||
|
||||
static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp,
|
||||
unsigned int mrq)
|
||||
{
|
||||
struct tegra_bpmp_mrq *entry;
|
||||
|
||||
list_for_each_entry(entry, &bpmp->mrqs, list)
|
||||
if (entry->mrq == mrq)
|
||||
return entry;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel,
|
||||
int code, const void *data, size_t size)
|
||||
{
|
||||
unsigned long flags = channel->ib->flags;
|
||||
struct tegra_bpmp *bpmp = channel->bpmp;
|
||||
struct tegra_bpmp_mb_data *frame;
|
||||
int err;
|
||||
|
||||
if (WARN_ON(size > MSG_DATA_MIN_SZ))
|
||||
return;
|
||||
|
||||
err = tegra_ivc_read_advance(channel->ivc);
|
||||
if (WARN_ON(err < 0))
|
||||
return;
|
||||
|
||||
if ((flags & MSG_ACK) == 0)
|
||||
return;
|
||||
|
||||
frame = tegra_ivc_write_get_next_frame(channel->ivc);
|
||||
if (WARN_ON(IS_ERR(frame)))
|
||||
return;
|
||||
|
||||
frame->code = code;
|
||||
|
||||
if (data && size > 0)
|
||||
memcpy(frame->data, data, size);
|
||||
|
||||
err = tegra_ivc_write_advance(channel->ivc);
|
||||
if (WARN_ON(err < 0))
|
||||
return;
|
||||
|
||||
if (flags & MSG_RING) {
|
||||
err = mbox_send_message(bpmp->mbox.channel, NULL);
|
||||
if (WARN_ON(err < 0))
|
||||
return;
|
||||
|
||||
mbox_client_txdone(bpmp->mbox.channel, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void tegra_bpmp_handle_mrq(struct tegra_bpmp *bpmp,
|
||||
unsigned int mrq,
|
||||
struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
struct tegra_bpmp_mrq *entry;
|
||||
u32 zero = 0;
|
||||
|
||||
spin_lock(&bpmp->lock);
|
||||
|
||||
entry = tegra_bpmp_find_mrq(bpmp, mrq);
|
||||
if (!entry) {
|
||||
spin_unlock(&bpmp->lock);
|
||||
tegra_bpmp_mrq_return(channel, -EINVAL, &zero, sizeof(zero));
|
||||
return;
|
||||
}
|
||||
|
||||
entry->handler(mrq, channel, entry->data);
|
||||
|
||||
spin_unlock(&bpmp->lock);
|
||||
}
|
||||
|
||||
int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq,
|
||||
tegra_bpmp_mrq_handler_t handler, void *data)
|
||||
{
|
||||
struct tegra_bpmp_mrq *entry;
|
||||
unsigned long flags;
|
||||
|
||||
if (!handler)
|
||||
return -EINVAL;
|
||||
|
||||
entry = devm_kzalloc(bpmp->dev, sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irqsave(&bpmp->lock, flags);
|
||||
|
||||
entry->mrq = mrq;
|
||||
entry->handler = handler;
|
||||
entry->data = data;
|
||||
list_add(&entry->list, &bpmp->mrqs);
|
||||
|
||||
spin_unlock_irqrestore(&bpmp->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_bpmp_request_mrq);
|
||||
|
||||
void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, void *data)
|
||||
{
|
||||
struct tegra_bpmp_mrq *entry;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&bpmp->lock, flags);
|
||||
|
||||
entry = tegra_bpmp_find_mrq(bpmp, mrq);
|
||||
if (!entry)
|
||||
goto unlock;
|
||||
|
||||
list_del(&entry->list);
|
||||
devm_kfree(bpmp->dev, entry);
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&bpmp->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tegra_bpmp_free_mrq);
|
||||
|
||||
static void tegra_bpmp_mrq_handle_ping(unsigned int mrq,
|
||||
struct tegra_bpmp_channel *channel,
|
||||
void *data)
|
||||
{
|
||||
struct mrq_ping_request *request;
|
||||
struct mrq_ping_response response;
|
||||
|
||||
request = (struct mrq_ping_request *)channel->ib->data;
|
||||
|
||||
memset(&response, 0, sizeof(response));
|
||||
response.reply = request->challenge << 1;
|
||||
|
||||
tegra_bpmp_mrq_return(channel, 0, &response, sizeof(response));
|
||||
}
|
||||
|
||||
static int tegra_bpmp_ping(struct tegra_bpmp *bpmp)
|
||||
{
|
||||
struct mrq_ping_response response;
|
||||
struct mrq_ping_request request;
|
||||
struct tegra_bpmp_message msg;
|
||||
unsigned long flags;
|
||||
ktime_t start, end;
|
||||
int err;
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.challenge = 1;
|
||||
|
||||
memset(&response, 0, sizeof(response));
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.mrq = MRQ_PING;
|
||||
msg.tx.data = &request;
|
||||
msg.tx.size = sizeof(request);
|
||||
msg.rx.data = &response;
|
||||
msg.rx.size = sizeof(response);
|
||||
|
||||
local_irq_save(flags);
|
||||
start = ktime_get();
|
||||
err = tegra_bpmp_transfer_atomic(bpmp, &msg);
|
||||
end = ktime_get();
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (!err)
|
||||
dev_dbg(bpmp->dev,
|
||||
"ping ok: challenge: %u, response: %u, time: %lld\n",
|
||||
request.challenge, response.reply,
|
||||
ktime_to_us(ktime_sub(end, start)));
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra_bpmp_get_firmware_tag(struct tegra_bpmp *bpmp, char *tag,
|
||||
size_t size)
|
||||
{
|
||||
struct mrq_query_tag_request request;
|
||||
struct tegra_bpmp_message msg;
|
||||
unsigned long flags;
|
||||
dma_addr_t phys;
|
||||
void *virt;
|
||||
int err;
|
||||
|
||||
virt = dma_alloc_coherent(bpmp->dev, MSG_DATA_MIN_SZ, &phys,
|
||||
GFP_KERNEL | GFP_DMA32);
|
||||
if (!virt)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.addr = phys;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.mrq = MRQ_QUERY_TAG;
|
||||
msg.tx.data = &request;
|
||||
msg.tx.size = sizeof(request);
|
||||
|
||||
local_irq_save(flags);
|
||||
err = tegra_bpmp_transfer_atomic(bpmp, &msg);
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (err == 0)
|
||||
strlcpy(tag, virt, size);
|
||||
|
||||
dma_free_coherent(bpmp->dev, MSG_DATA_MIN_SZ, virt, phys);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tegra_bpmp_channel_signal(struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
unsigned long flags = channel->ob->flags;
|
||||
|
||||
if ((flags & MSG_RING) == 0)
|
||||
return;
|
||||
|
||||
complete(&channel->completion);
|
||||
}
|
||||
|
||||
static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data)
|
||||
{
|
||||
struct tegra_bpmp *bpmp = mbox_client_to_bpmp(client);
|
||||
struct tegra_bpmp_channel *channel;
|
||||
unsigned int i, count;
|
||||
unsigned long *busy;
|
||||
|
||||
channel = tegra_bpmp_channel_get_rx(bpmp);
|
||||
count = bpmp->soc->channels.thread.count;
|
||||
busy = bpmp->threaded.busy;
|
||||
|
||||
if (tegra_bpmp_master_acked(channel))
|
||||
tegra_bpmp_handle_mrq(bpmp, channel->ib->code, channel);
|
||||
|
||||
spin_lock(&bpmp->lock);
|
||||
|
||||
for_each_set_bit(i, busy, count) {
|
||||
struct tegra_bpmp_channel *channel;
|
||||
|
||||
channel = tegra_bpmp_channel_get_thread(bpmp, i);
|
||||
if (!channel)
|
||||
continue;
|
||||
|
||||
if (tegra_bpmp_master_acked(channel)) {
|
||||
tegra_bpmp_channel_signal(channel);
|
||||
clear_bit(i, busy);
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&bpmp->lock);
|
||||
}
|
||||
|
||||
static void tegra_bpmp_ivc_notify(struct tegra_ivc *ivc, void *data)
|
||||
{
|
||||
struct tegra_bpmp *bpmp = data;
|
||||
int err;
|
||||
|
||||
if (WARN_ON(bpmp->mbox.channel == NULL))
|
||||
return;
|
||||
|
||||
err = mbox_send_message(bpmp->mbox.channel, NULL);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
mbox_client_txdone(bpmp->mbox.channel, 0);
|
||||
}
|
||||
|
||||
static int tegra_bpmp_channel_init(struct tegra_bpmp_channel *channel,
|
||||
struct tegra_bpmp *bpmp,
|
||||
unsigned int index)
|
||||
{
|
||||
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,
|
||||
bpmp->rx.virt + offset, bpmp->rx.phys + offset,
|
||||
bpmp->tx.virt + offset, bpmp->tx.phys + offset,
|
||||
1, message_size, tegra_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 tegra_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 tegra_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel)
|
||||
{
|
||||
tegra_ivc_cleanup(channel->ivc);
|
||||
}
|
||||
|
||||
static int tegra_bpmp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_bpmp_channel *channel;
|
||||
struct tegra_bpmp *bpmp;
|
||||
unsigned int i;
|
||||
char tag[32];
|
||||
size_t size;
|
||||
int err;
|
||||
|
||||
bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
|
||||
if (!bpmp)
|
||||
return -ENOMEM;
|
||||
|
||||
bpmp->soc = of_device_get_match_data(&pdev->dev);
|
||||
bpmp->dev = &pdev->dev;
|
||||
|
||||
bpmp->tx.pool = of_gen_pool_get(pdev->dev.of_node, "shmem", 0);
|
||||
if (!bpmp->tx.pool) {
|
||||
dev_err(&pdev->dev, "TX shmem pool not found\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
bpmp->tx.virt = gen_pool_dma_alloc(bpmp->tx.pool, 4096, &bpmp->tx.phys);
|
||||
if (!bpmp->tx.virt) {
|
||||
dev_err(&pdev->dev, "failed to allocate from TX pool\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
bpmp->rx.pool = of_gen_pool_get(pdev->dev.of_node, "shmem", 1);
|
||||
if (!bpmp->rx.pool) {
|
||||
dev_err(&pdev->dev, "RX shmem pool not found\n");
|
||||
err = -ENOMEM;
|
||||
goto free_tx;
|
||||
}
|
||||
|
||||
bpmp->rx.virt = gen_pool_dma_alloc(bpmp->rx.pool, 4096, &bpmp->rx.phys);
|
||||
if (!bpmp->rx.pool) {
|
||||
dev_err(&pdev->dev, "failed to allocate from RX pool\n");
|
||||
err = -ENOMEM;
|
||||
goto free_tx;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&bpmp->mrqs);
|
||||
spin_lock_init(&bpmp->lock);
|
||||
|
||||
bpmp->threaded.count = bpmp->soc->channels.thread.count;
|
||||
sema_init(&bpmp->threaded.lock, bpmp->threaded.count);
|
||||
|
||||
size = BITS_TO_LONGS(bpmp->threaded.count) * sizeof(long);
|
||||
|
||||
bpmp->threaded.allocated = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (!bpmp->threaded.allocated) {
|
||||
err = -ENOMEM;
|
||||
goto free_rx;
|
||||
}
|
||||
|
||||
bpmp->threaded.busy = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
|
||||
if (!bpmp->threaded.busy) {
|
||||
err = -ENOMEM;
|
||||
goto free_rx;
|
||||
}
|
||||
|
||||
bpmp->num_channels = bpmp->soc->channels.cpu_tx.count +
|
||||
bpmp->soc->channels.thread.count +
|
||||
bpmp->soc->channels.cpu_rx.count;
|
||||
|
||||
bpmp->channels = devm_kcalloc(&pdev->dev, bpmp->num_channels,
|
||||
sizeof(*channel), GFP_KERNEL);
|
||||
if (!bpmp->channels) {
|
||||
err = -ENOMEM;
|
||||
goto free_rx;
|
||||
}
|
||||
|
||||
/* message channel initialization */
|
||||
for (i = 0; i < bpmp->num_channels; i++) {
|
||||
struct tegra_bpmp_channel *channel = &bpmp->channels[i];
|
||||
|
||||
err = tegra_bpmp_channel_init(channel, bpmp, i);
|
||||
if (err < 0)
|
||||
goto cleanup_channels;
|
||||
}
|
||||
|
||||
/* mbox registration */
|
||||
bpmp->mbox.client.dev = &pdev->dev;
|
||||
bpmp->mbox.client.rx_callback = tegra_bpmp_handle_rx;
|
||||
bpmp->mbox.client.tx_block = false;
|
||||
bpmp->mbox.client.knows_txdone = false;
|
||||
|
||||
bpmp->mbox.channel = mbox_request_channel(&bpmp->mbox.client, 0);
|
||||
if (IS_ERR(bpmp->mbox.channel)) {
|
||||
err = PTR_ERR(bpmp->mbox.channel);
|
||||
dev_err(&pdev->dev, "failed to get HSP mailbox: %d\n", err);
|
||||
goto cleanup_channels;
|
||||
}
|
||||
|
||||
/* reset message channels */
|
||||
for (i = 0; i < bpmp->num_channels; i++) {
|
||||
struct tegra_bpmp_channel *channel = &bpmp->channels[i];
|
||||
|
||||
tegra_bpmp_channel_reset(channel);
|
||||
}
|
||||
|
||||
err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
|
||||
tegra_bpmp_mrq_handle_ping, bpmp);
|
||||
if (err < 0)
|
||||
goto free_mbox;
|
||||
|
||||
err = tegra_bpmp_ping(bpmp);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to ping BPMP: %d\n", err);
|
||||
goto free_mrq;
|
||||
}
|
||||
|
||||
err = tegra_bpmp_get_firmware_tag(bpmp, tag, sizeof(tag) - 1);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to get firmware tag: %d\n", err);
|
||||
goto free_mrq;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "firmware: %s\n", tag);
|
||||
|
||||
err = of_platform_default_populate(pdev->dev.of_node, NULL, &pdev->dev);
|
||||
if (err < 0)
|
||||
goto free_mrq;
|
||||
|
||||
err = tegra_bpmp_init_clocks(bpmp);
|
||||
if (err < 0)
|
||||
goto free_mrq;
|
||||
|
||||
err = tegra_bpmp_init_resets(bpmp);
|
||||
if (err < 0)
|
||||
goto free_mrq;
|
||||
|
||||
platform_set_drvdata(pdev, bpmp);
|
||||
|
||||
return 0;
|
||||
|
||||
free_mrq:
|
||||
tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
|
||||
free_mbox:
|
||||
mbox_free_channel(bpmp->mbox.channel);
|
||||
cleanup_channels:
|
||||
while (i--)
|
||||
tegra_bpmp_channel_cleanup(&bpmp->channels[i]);
|
||||
free_rx:
|
||||
gen_pool_free(bpmp->rx.pool, (unsigned long)bpmp->rx.virt, 4096);
|
||||
free_tx:
|
||||
gen_pool_free(bpmp->tx.pool, (unsigned long)bpmp->tx.virt, 4096);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct tegra_bpmp_soc tegra186_soc = {
|
||||
.channels = {
|
||||
.cpu_tx = {
|
||||
.offset = 0,
|
||||
.count = 6,
|
||||
.timeout = 60 * USEC_PER_SEC,
|
||||
},
|
||||
.thread = {
|
||||
.offset = 6,
|
||||
.count = 7,
|
||||
.timeout = 600 * USEC_PER_SEC,
|
||||
},
|
||||
.cpu_rx = {
|
||||
.offset = 13,
|
||||
.count = 1,
|
||||
.timeout = 0,
|
||||
},
|
||||
},
|
||||
.num_resets = 193,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra_bpmp_match[] = {
|
||||
{ .compatible = "nvidia,tegra186-bpmp", .data = &tegra186_soc },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct platform_driver tegra_bpmp_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-bpmp",
|
||||
.of_match_table = tegra_bpmp_match,
|
||||
},
|
||||
.probe = tegra_bpmp_probe,
|
||||
};
|
||||
|
||||
static int __init tegra_bpmp_init(void)
|
||||
{
|
||||
return platform_driver_register(&tegra_bpmp_driver);
|
||||
}
|
||||
core_initcall(tegra_bpmp_init);
|
1601
include/soc/tegra/bpmp-abi.h
Normal file
1601
include/soc/tegra/bpmp-abi.h
Normal file
File diff suppressed because it is too large
Load Diff
141
include/soc/tegra/bpmp.h
Normal file
141
include/soc/tegra/bpmp.h
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#ifndef __SOC_TEGRA_BPMP_H
|
||||
#define __SOC_TEGRA_BPMP_H
|
||||
|
||||
#include <linux/mailbox_client.h>
|
||||
#include <linux/reset-controller.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <soc/tegra/bpmp-abi.h>
|
||||
|
||||
struct tegra_bpmp_clk;
|
||||
|
||||
struct tegra_bpmp_soc {
|
||||
struct {
|
||||
struct {
|
||||
unsigned int offset;
|
||||
unsigned int count;
|
||||
unsigned int timeout;
|
||||
} cpu_tx, thread, cpu_rx;
|
||||
} channels;
|
||||
unsigned int num_resets;
|
||||
};
|
||||
|
||||
struct tegra_bpmp_mb_data {
|
||||
u32 code;
|
||||
u32 flags;
|
||||
u8 data[MSG_DATA_MIN_SZ];
|
||||
} __packed;
|
||||
|
||||
struct tegra_bpmp_channel {
|
||||
struct tegra_bpmp *bpmp;
|
||||
struct tegra_bpmp_mb_data *ib;
|
||||
struct tegra_bpmp_mb_data *ob;
|
||||
struct completion completion;
|
||||
struct tegra_ivc *ivc;
|
||||
};
|
||||
|
||||
typedef void (*tegra_bpmp_mrq_handler_t)(unsigned int mrq,
|
||||
struct tegra_bpmp_channel *channel,
|
||||
void *data);
|
||||
|
||||
struct tegra_bpmp_mrq {
|
||||
struct list_head list;
|
||||
unsigned int mrq;
|
||||
tegra_bpmp_mrq_handler_t handler;
|
||||
void *data;
|
||||
};
|
||||
|
||||
struct tegra_bpmp {
|
||||
const struct tegra_bpmp_soc *soc;
|
||||
struct device *dev;
|
||||
|
||||
struct {
|
||||
struct gen_pool *pool;
|
||||
dma_addr_t phys;
|
||||
void *virt;
|
||||
} tx, rx;
|
||||
|
||||
struct {
|
||||
struct mbox_client client;
|
||||
struct mbox_chan *channel;
|
||||
} mbox;
|
||||
|
||||
struct tegra_bpmp_channel *channels;
|
||||
unsigned int num_channels;
|
||||
|
||||
struct {
|
||||
unsigned long *allocated;
|
||||
unsigned long *busy;
|
||||
unsigned int count;
|
||||
struct semaphore lock;
|
||||
} threaded;
|
||||
|
||||
struct list_head mrqs;
|
||||
spinlock_t lock;
|
||||
|
||||
struct tegra_bpmp_clk **clocks;
|
||||
unsigned int num_clocks;
|
||||
|
||||
struct reset_controller_dev rstc;
|
||||
};
|
||||
|
||||
struct tegra_bpmp *tegra_bpmp_get(struct device *dev);
|
||||
void tegra_bpmp_put(struct tegra_bpmp *bpmp);
|
||||
|
||||
struct tegra_bpmp_message {
|
||||
unsigned int mrq;
|
||||
|
||||
struct {
|
||||
const void *data;
|
||||
size_t size;
|
||||
} tx;
|
||||
|
||||
struct {
|
||||
void *data;
|
||||
size_t size;
|
||||
} rx;
|
||||
};
|
||||
|
||||
int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
|
||||
struct tegra_bpmp_message *msg);
|
||||
int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
|
||||
struct tegra_bpmp_message *msg);
|
||||
|
||||
int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq,
|
||||
tegra_bpmp_mrq_handler_t handler, void *data);
|
||||
void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, unsigned int mrq,
|
||||
void *data);
|
||||
|
||||
#if IS_ENABLED(CONFIG_CLK_TEGRA_BPMP)
|
||||
int tegra_bpmp_init_clocks(struct tegra_bpmp *bpmp);
|
||||
#else
|
||||
static inline int tegra_bpmp_init_clocks(struct tegra_bpmp *bpmp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_RESET_TEGRA_BPMP)
|
||||
int tegra_bpmp_init_resets(struct tegra_bpmp *bpmp);
|
||||
#else
|
||||
static inline int tegra_bpmp_init_resets(struct tegra_bpmp *bpmp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __SOC_TEGRA_BPMP_H */
|
Loading…
Reference in New Issue
Block a user