mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-23 09:42:19 +03:00
nvidia-oot: add psc kernel module
Bug 4036670 Change-Id: I619baafc4625cb604d87c0fe0e0ec4368bc0b837 Signed-off-by: Liang Cheng <licheng@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2878778 GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com> Reviewed-by: Bitan Biswas <bbiswas@nvidia.com> Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
This commit is contained in:
committed by
mobile promotions
parent
ba066d3e58
commit
b4b748f294
@@ -29,4 +29,5 @@ endif
|
||||
obj-m += mc-hwpm.o
|
||||
obj-m += mc-utils/
|
||||
obj-m += dce/
|
||||
obj-m += psc/
|
||||
obj-m += rtcpu/
|
||||
|
||||
14
drivers/platform/tegra/psc/Makefile
Normal file
14
drivers/platform/tegra/psc/Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
|
||||
GCOV_PROFILE := y
|
||||
CONFIG_TEGRA_PSC := m
|
||||
|
||||
ccflags-y += -I$(srctree.nvidia)/drivers/platform/tegra/include
|
||||
|
||||
ccflags-y += -Werror
|
||||
|
||||
obj-$(CONFIG_TEGRA_PSC) += tegra23x_psc.o
|
||||
tegra23x_psc-objs := tegra23x_psc_mailbox.o
|
||||
|
||||
tegra23x_psc-$(CONFIG_DEBUG_FS) += psc_debug.o
|
||||
400
drivers/platform/tegra/psc/psc_debug.c
Normal file
400
drivers/platform/tegra/psc/psc_debug.c
Normal file
@@ -0,0 +1,400 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/mailbox_client.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/completion.h>
|
||||
#include "tegra23x_psc.h"
|
||||
|
||||
/* EXT_CFG register offset */
|
||||
#define EXT_CFG_SIDTABLE 0x0
|
||||
#define EXT_CFG_SIDCONFIG 0x4
|
||||
|
||||
#define MBOX_MSG_LEN 64
|
||||
|
||||
#define RX_READY 1
|
||||
#define RX_IDLE 0
|
||||
|
||||
/* Max block period in ms before TX is assumed failure. */
|
||||
#define DEFAULT_TX_TIMEOUT 2000
|
||||
|
||||
/* 256MB max size to use for dma_alloc* */
|
||||
#define MAX_SHARED_MEM (256 * 1024 * 1024U)
|
||||
|
||||
struct xfer_info {
|
||||
__u32 opcode[2];
|
||||
void __user *tx_buf;
|
||||
void __user *rx_buf;
|
||||
|
||||
__u32 tx_size;
|
||||
__u32 rx_size;
|
||||
|
||||
__u8 out[MBOX_MSG_LEN];
|
||||
};
|
||||
|
||||
union mbox_msg {
|
||||
struct {
|
||||
u32 opcode[2];
|
||||
u32 tx_size;
|
||||
u32 rx_size;
|
||||
u64 tx_iova;
|
||||
u64 rx_iova;
|
||||
};
|
||||
u32 data[16];
|
||||
};
|
||||
|
||||
#define PSCDBG_IOCTL_BASE 'P'
|
||||
#define PSCIOC_XFER_DATA _IOWR(PSCDBG_IOCTL_BASE, 0, struct xfer_info)
|
||||
|
||||
struct psc_debug_dev {
|
||||
struct mutex lock;
|
||||
struct platform_device *pdev;
|
||||
struct mbox_client cl;
|
||||
struct mbox_chan *chan;
|
||||
struct completion rx_complete;
|
||||
|
||||
u8 rx_msg[MBOX_MSG_LEN];
|
||||
struct mbox_controller *mbox; /* our mbox controller */
|
||||
|
||||
bool is_cfg_inited; /* did we initialize SIDTABLE, etc? */
|
||||
};
|
||||
|
||||
static struct psc_debug_dev psc_debug;
|
||||
static struct dentry *debugfs_root;
|
||||
|
||||
#define NV(x) "nvidia," #x
|
||||
static int
|
||||
setup_extcfg(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
u32 value;
|
||||
|
||||
/* second mailbox address */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "extcfg");
|
||||
/* we have res == 0 in case of ACPI and not DT */
|
||||
if (res == NULL)
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(base))
|
||||
return -EINVAL;
|
||||
|
||||
dev_info(&pdev->dev, "ext_cfg base:%p\n", base);
|
||||
|
||||
if (!device_property_read_u8_array(&pdev->dev, NV(sidtable),
|
||||
(u8 *)&value, sizeof(value))) {
|
||||
dev_dbg(&pdev->dev, "sidtable:%08x\n", value);
|
||||
writel0(value, base + EXT_CFG_SIDTABLE); /* PSC_EXT_CFG_SIDTABLE_VM0_0 */
|
||||
}
|
||||
|
||||
if (!device_property_read_u32(&pdev->dev, NV(sidconfig), &value)) {
|
||||
dev_dbg(&pdev->dev, "sidcfg:%08x\n", value);
|
||||
writel0(value, base + EXT_CFG_SIDCONFIG); /* PSC_EXT_CFG_SIDCONFIG_VM0_0 */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int psc_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct mbox_chan *chan;
|
||||
struct psc_debug_dev *dbg = inode->i_private;
|
||||
struct platform_device *pdev = dbg->pdev;
|
||||
int ret = 0;
|
||||
|
||||
if (mutex_lock_interruptible(&dbg->lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (dbg->is_cfg_inited == false) {
|
||||
dbg->is_cfg_inited = true;
|
||||
setup_extcfg(pdev);
|
||||
}
|
||||
|
||||
file->private_data = dbg;
|
||||
|
||||
chan = psc_mbox_request_channel0(dbg->mbox, &dbg->cl);
|
||||
if (IS_ERR(chan) && (PTR_ERR(chan) != -EPROBE_DEFER)) {
|
||||
dev_err(&pdev->dev, "failed to get channel, err %lx\n",
|
||||
PTR_ERR(chan));
|
||||
ret = PTR_ERR(chan);
|
||||
goto return_unlock;
|
||||
}
|
||||
|
||||
dbg->chan = chan;
|
||||
init_completion(&dbg->rx_complete);
|
||||
nonseekable_open(inode, file);
|
||||
|
||||
return_unlock:
|
||||
mutex_unlock(&dbg->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int psc_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct psc_debug_dev *dbg = file->private_data;
|
||||
|
||||
mutex_lock(&dbg->lock);
|
||||
|
||||
mbox_free_channel(dbg->chan);
|
||||
|
||||
file->private_data = NULL;
|
||||
|
||||
mutex_unlock(&dbg->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t psc_debug_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct psc_debug_dev *dbg = file->private_data;
|
||||
ssize_t ret;
|
||||
loff_t pos = 0;
|
||||
|
||||
if (count > MBOX_MSG_LEN)
|
||||
return -EINTR;
|
||||
|
||||
mutex_lock(&dbg->lock);
|
||||
|
||||
ret = simple_read_from_buffer(buffer, count, &pos,
|
||||
dbg->rx_msg, min_t(size_t, count, MBOX_MSG_LEN));
|
||||
*ppos += pos;
|
||||
|
||||
mutex_unlock(&dbg->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int send_msg_block(struct psc_debug_dev *dbg, void *tx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
reinit_completion(&dbg->rx_complete);
|
||||
|
||||
ret = mbox_send_message(dbg->chan, tx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mbox_client_txdone(dbg->chan, 0);
|
||||
ret = wait_for_completion_timeout(&dbg->rx_complete,
|
||||
msecs_to_jiffies(dbg->cl.tx_tout));
|
||||
if (ret == 0) {
|
||||
pr_info("%s:%d wait_for_completion_timeout timed out!\n", __func__, __LINE__);
|
||||
return -ETIME;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t psc_debug_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct psc_debug_dev *dbg = file->private_data;
|
||||
struct platform_device *pdev = dbg->pdev;
|
||||
u8 tx_buf[MBOX_MSG_LEN] = { 0 };
|
||||
ssize_t ret;
|
||||
|
||||
if (count > MBOX_MSG_LEN) {
|
||||
dev_err(&pdev->dev, "write size > MBOX_MSG_LEN\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&dbg->lock);
|
||||
if (copy_from_user(tx_buf, buffer, count)) {
|
||||
dev_err(&pdev->dev, "copy_from_user() error!\n");
|
||||
ret = -EFAULT;
|
||||
goto return_unlock;
|
||||
}
|
||||
ret = send_msg_block(dbg, tx_buf);
|
||||
|
||||
return_unlock:
|
||||
mutex_unlock(&dbg->lock);
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
static long xfer_data(struct file *file, char __user *data)
|
||||
{
|
||||
struct psc_debug_dev *dbg = file->private_data;
|
||||
struct platform_device *pdev = dbg->pdev;
|
||||
struct device *dev = &pdev->dev;
|
||||
void *tx_virt = NULL;
|
||||
void *rx_virt = NULL;
|
||||
dma_addr_t tx_phys = 0;
|
||||
dma_addr_t rx_phys = 0;
|
||||
long ret = 0;
|
||||
union mbox_msg msg = {};
|
||||
struct xfer_info info;
|
||||
struct xfer_info __user *ptr_xfer = (struct xfer_info __user *)data;
|
||||
|
||||
if (copy_from_user(&info, data, sizeof(struct xfer_info))) {
|
||||
dev_err(&pdev->dev, "failed to copy data.\n");
|
||||
ret = -EFAULT;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "opcode[%x %x]\n", info.opcode[0], info.opcode[1]);
|
||||
dev_dbg(dev, "tx[%p, size:%u], rx[%p, size:%u]\n",
|
||||
info.tx_buf, info.tx_size, info.rx_buf, info.rx_size);
|
||||
|
||||
if (info.tx_size > MAX_SHARED_MEM || info.rx_size > MAX_SHARED_MEM)
|
||||
return -ENOMEM;
|
||||
|
||||
if (info.tx_buf && info.tx_size > 0) {
|
||||
tx_virt = dma_alloc_coherent(dev, info.tx_size,
|
||||
&tx_phys, GFP_KERNEL);
|
||||
if (tx_virt == NULL || tx_phys == 0) {
|
||||
dev_err(dev, "dma_alloc_coherent() failed!\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (copy_from_user(tx_virt, info.tx_buf, info.tx_size)) {
|
||||
dev_err(dev, "failed to copy data.\n");
|
||||
ret = -EFAULT;
|
||||
goto free_rx;
|
||||
}
|
||||
dma_sync_single_for_device(dev, tx_phys,
|
||||
info.tx_size, DMA_TO_DEVICE);
|
||||
}
|
||||
|
||||
if (info.rx_buf && info.rx_size > 0) {
|
||||
rx_virt = dma_alloc_coherent(dev, info.rx_size,
|
||||
&rx_phys, GFP_KERNEL);
|
||||
if (rx_virt == NULL || rx_phys == 0) {
|
||||
dev_err(dev, "dma_alloc_coherent() failed!\n");
|
||||
ret = -ENOMEM;
|
||||
goto free_tx;
|
||||
}
|
||||
}
|
||||
dev_dbg(dev, "tx_virt:%p, tx_phys: %p\n", tx_virt, (void *)tx_phys);
|
||||
dev_dbg(dev, "rx_virt:%p, rx_phys: %p\n", rx_virt, (void *)rx_phys);
|
||||
|
||||
msg.opcode[0] = info.opcode[0];
|
||||
msg.opcode[1] = info.opcode[1];
|
||||
msg.tx_iova = tx_phys;
|
||||
msg.rx_iova = rx_phys;
|
||||
msg.tx_size = info.tx_size;
|
||||
msg.rx_size = info.rx_size;
|
||||
|
||||
ret = send_msg_block(dbg, &msg);
|
||||
if (ret != 0)
|
||||
goto free_rx;
|
||||
|
||||
/* copy mbox payload */
|
||||
if (copy_to_user(&ptr_xfer->out[0],
|
||||
&dbg->rx_msg[0], sizeof(dbg->rx_msg))) {
|
||||
dev_err(dev, "failed to mbox out data.\n");
|
||||
ret = -EFAULT;
|
||||
goto free_rx;
|
||||
}
|
||||
|
||||
if (rx_phys && info.rx_size > 0) {
|
||||
dma_sync_single_for_cpu(dev, rx_phys,
|
||||
info.rx_size, DMA_BIDIRECTIONAL);
|
||||
|
||||
if (copy_to_user(info.rx_buf, rx_virt, msg.rx_size)) {
|
||||
dev_err(dev, "failed to copy_to_user.\n");
|
||||
ret = -EFAULT;
|
||||
goto free_rx;
|
||||
}
|
||||
}
|
||||
|
||||
free_rx:
|
||||
if (rx_phys)
|
||||
dma_free_coherent(dev, info.rx_size, rx_virt, rx_phys);
|
||||
free_tx:
|
||||
if (tx_phys)
|
||||
dma_free_coherent(dev, info.tx_size, tx_virt, tx_phys);
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long
|
||||
psc_debug_ioctl(struct file *file, unsigned int cmd, unsigned long data)
|
||||
{
|
||||
struct psc_debug_dev *dbg = file->private_data;
|
||||
long ret = -ENOIOCTLCMD;
|
||||
|
||||
mutex_lock(&dbg->lock);
|
||||
|
||||
switch (cmd) {
|
||||
case PSCIOC_XFER_DATA:
|
||||
ret = xfer_data(file, (char __user *)data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&dbg->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations psc_debug_fops = {
|
||||
.open = psc_debug_open,
|
||||
.read = psc_debug_read,
|
||||
.write = psc_debug_write,
|
||||
.release = psc_debug_release,
|
||||
.unlocked_ioctl = psc_debug_ioctl,
|
||||
};
|
||||
|
||||
static void psc_chan_rx_callback(struct mbox_client *c, void *msg)
|
||||
{
|
||||
struct device *dev = c->dev;
|
||||
struct psc_debug_dev *dbg = container_of(c, struct psc_debug_dev, cl);
|
||||
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
|
||||
memcpy(dbg->rx_msg, msg, MBOX_MSG_LEN);
|
||||
complete(&dbg->rx_complete);
|
||||
}
|
||||
|
||||
int psc_debugfs_create(struct platform_device *pdev, struct mbox_controller *mbox)
|
||||
{
|
||||
struct psc_debug_dev *dbg = &psc_debug;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
if (!debugfs_initialized()) {
|
||||
dev_err(dev, "debugfs is not initialized\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
debugfs_root = debugfs_create_dir("psc", NULL);
|
||||
if (debugfs_root == NULL) {
|
||||
dev_err(dev, "failed to create psc debugfs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dbg->cl.dev = dev;
|
||||
dbg->cl.rx_callback = psc_chan_rx_callback;
|
||||
dbg->cl.tx_block = false;
|
||||
dbg->cl.tx_tout = DEFAULT_TX_TIMEOUT;
|
||||
dbg->cl.knows_txdone = false;
|
||||
dbg->pdev = pdev;
|
||||
dbg->mbox = mbox; /* our controller */
|
||||
dbg->is_cfg_inited = false;
|
||||
|
||||
mutex_init(&dbg->lock);
|
||||
|
||||
debugfs_create_x64("tx_timeout", 0644, debugfs_root,
|
||||
(u64 *)&dbg->cl.tx_tout);
|
||||
debugfs_create_file("mbox_dbg", 0600, debugfs_root,
|
||||
dbg, &psc_debug_fops);
|
||||
|
||||
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void psc_debugfs_remove(struct platform_device *pdev)
|
||||
{
|
||||
dev_dbg(&pdev->dev, "%s\n", __func__);
|
||||
|
||||
mutex_destroy(&psc_debug.lock);
|
||||
debugfs_remove_recursive(debugfs_root);
|
||||
}
|
||||
30
drivers/platform/tegra/psc/tegra23x_psc.h
Normal file
30
drivers/platform/tegra/psc/tegra23x_psc.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
|
||||
struct mbox_controller;
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS)
|
||||
int psc_debugfs_create(struct platform_device *pdev, struct mbox_controller *mbox);
|
||||
void psc_debugfs_remove(struct platform_device *pdev);
|
||||
#else
|
||||
/* We should probably never be here as this driver is useless without debugfs */
|
||||
static inline int psc_debugfs_create(struct platform_device *pdev, struct mbox_controller *mbox)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void psc_debugfs_remove(struct platform_device *pdev) { return; }
|
||||
#endif
|
||||
|
||||
struct mbox_chan *psc_mbox_request_channel0(struct mbox_controller *mbox, struct mbox_client *cl);
|
||||
|
||||
#if IS_ENABLED(CONFIG_NUMA)
|
||||
#define PSC_HAVE_NUMA
|
||||
#endif
|
||||
|
||||
#if defined(PSC_HAVE_NUMA)
|
||||
u32 readl0(void __iomem *addr);
|
||||
void writel0(u32 value, void __iomem *addr);
|
||||
#else
|
||||
#define readl0(addr) readl((addr))
|
||||
#define writel0(value, addr) writel((value), (addr))
|
||||
#endif
|
||||
364
drivers/platform/tegra/psc/tegra23x_psc_mailbox.c
Normal file
364
drivers/platform/tegra/psc/tegra23x_psc_mailbox.c
Normal file
@@ -0,0 +1,364 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mailbox_controller.h>
|
||||
#include <linux/mailbox_client.h>
|
||||
|
||||
/* from drivers/mailbox/mailbox.h */
|
||||
#define TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */
|
||||
#define TXDONE_BY_ACK BIT(2) /* S/W ACK received by Client ticks the TX */
|
||||
|
||||
#include "tegra23x_psc.h"
|
||||
|
||||
#define MBOX_NUM 8
|
||||
#define MBOX_REG_OFFSET 0x10000
|
||||
/* 16 32-bit registers for MBOX_CHAN_IN/OUT */
|
||||
#define MBOX_MSG_SIZE 16
|
||||
|
||||
#define MBOX_CHAN_ID 0x0
|
||||
|
||||
#define MBOX_CHAN_EXT_CTRL 0x4
|
||||
#define MBOX_CHAN_PSC_CTRL 0x8
|
||||
/* bit to indicate remote that IN parameters are ready. */
|
||||
#define MBOX_IN_VALID BIT(0)
|
||||
/* bit to indicate remote that OUT parameters are read out */
|
||||
#define MBOX_OUT_DONE BIT(4)
|
||||
#define LIC_INTR_EN BIT(8)
|
||||
#define MBOX_OUT_VALID BIT(0)
|
||||
|
||||
#define MBOX_CHAN_TX 0x800
|
||||
#define MBOX_CHAN_RX 0x1000
|
||||
|
||||
struct psc_mbox;
|
||||
|
||||
struct mbox_vm_chan {
|
||||
unsigned int irq;
|
||||
void __iomem *base;
|
||||
struct psc_mbox *parent;
|
||||
};
|
||||
|
||||
struct psc_mbox {
|
||||
struct device *dev;
|
||||
void __iomem *vm_chan_base;
|
||||
struct mbox_chan chan[MBOX_NUM];
|
||||
struct mbox_controller mbox;
|
||||
struct mbox_vm_chan vm_chan[MBOX_NUM];
|
||||
};
|
||||
|
||||
|
||||
#ifdef PSC_HAVE_NUMA
|
||||
|
||||
struct io_data {
|
||||
u32 value;
|
||||
void __iomem *addr;
|
||||
};
|
||||
|
||||
static const struct cpumask *node0mask;
|
||||
|
||||
static void write_callback(void *param)
|
||||
{
|
||||
struct io_data *data = param;
|
||||
|
||||
writel(data->value, data->addr);
|
||||
}
|
||||
|
||||
void writel0(u32 value, void __iomem *addr)
|
||||
{
|
||||
struct io_data d = {
|
||||
.value = value, .addr = addr
|
||||
};
|
||||
|
||||
if (cpumask_test_cpu(smp_processor_id(), node0mask))
|
||||
writel(value, addr); /* optimization: direct call */
|
||||
else
|
||||
smp_call_function_any(node0mask, write_callback, &d, 1);
|
||||
}
|
||||
|
||||
static void read_callback(void *param)
|
||||
{
|
||||
struct io_data *data = param;
|
||||
|
||||
data->value = readl(data->addr);
|
||||
}
|
||||
|
||||
u32 readl0(void __iomem *addr)
|
||||
{
|
||||
struct io_data d = {
|
||||
.addr = addr
|
||||
};
|
||||
|
||||
if (cpumask_test_cpu(smp_processor_id(), node0mask))
|
||||
return readl(addr); /* optimization: direct call */
|
||||
|
||||
smp_call_function_any(node0mask, read_callback, &d, 1);
|
||||
return d.value;
|
||||
}
|
||||
#endif // PSC_HAVE_NUMA
|
||||
|
||||
static irqreturn_t psc_mbox_rx_interrupt(int irq, void *p)
|
||||
{
|
||||
u32 data[MBOX_MSG_SIZE];
|
||||
struct mbox_chan *chan = p;
|
||||
struct mbox_vm_chan *vm_chan = chan->con_priv;
|
||||
struct device *dev = vm_chan->parent->dev;
|
||||
u32 ext_ctrl;
|
||||
u32 psc_ctrl;
|
||||
int i;
|
||||
|
||||
psc_ctrl = readl(vm_chan->base + MBOX_CHAN_PSC_CTRL);
|
||||
/* not a valid case but it does happen. */
|
||||
if ((psc_ctrl & MBOX_OUT_VALID) == 0) {
|
||||
ext_ctrl = readl(vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
dev_err_once(dev, "invalid interrupt, psc_ctrl: 0x%08x ext_ctrl: 0x%08x\n",
|
||||
psc_ctrl, ext_ctrl);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
for (i = 0; i < MBOX_MSG_SIZE; i++)
|
||||
data[i] = readl(vm_chan->base + MBOX_CHAN_RX + i * 4);
|
||||
|
||||
mbox_chan_received_data(chan, data);
|
||||
/* finish read */
|
||||
ext_ctrl = readl(vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
ext_ctrl |= MBOX_OUT_DONE;
|
||||
writel(ext_ctrl, vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int psc_mbox_send_data(struct mbox_chan *chan, void *data)
|
||||
{
|
||||
struct mbox_vm_chan *vm_chan = chan->con_priv;
|
||||
struct device *dev = vm_chan->parent->dev;
|
||||
u32 *buf = data;
|
||||
int i;
|
||||
u32 ext_ctrl;
|
||||
|
||||
ext_ctrl = readl0(vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
|
||||
if ((ext_ctrl & MBOX_IN_VALID) != 0) {
|
||||
dev_err(dev, "%s:pending write.\n", __func__);
|
||||
return -EBUSY;
|
||||
}
|
||||
for (i = 0; i < MBOX_MSG_SIZE; i++)
|
||||
writel0(buf[i], vm_chan->base + MBOX_CHAN_TX + i * 4);
|
||||
|
||||
ext_ctrl |= MBOX_IN_VALID;
|
||||
writel0(ext_ctrl, vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_mbox_startup(struct mbox_chan *chan)
|
||||
{
|
||||
struct mbox_vm_chan *vm_chan = chan->con_priv;
|
||||
u32 ext_ctrl = LIC_INTR_EN;
|
||||
|
||||
writel0(ext_ctrl, vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
chan->txdone_method = TXDONE_BY_ACK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void psc_mbox_shutdown(struct mbox_chan *chan)
|
||||
{
|
||||
struct mbox_vm_chan *vm_chan;
|
||||
struct device *dev;
|
||||
|
||||
if (chan == NULL) {
|
||||
pr_err("%s: chan == NULL, exiting\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
vm_chan = chan->con_priv;
|
||||
dev = vm_chan->parent->dev;
|
||||
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
writel0(0, vm_chan->base + MBOX_CHAN_EXT_CTRL);
|
||||
}
|
||||
|
||||
static const struct mbox_chan_ops psc_mbox_ops = {
|
||||
.send_data = psc_mbox_send_data,
|
||||
.startup = psc_mbox_startup,
|
||||
.shutdown = psc_mbox_shutdown,
|
||||
};
|
||||
|
||||
static int tegra234_psc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct psc_mbox *psc;
|
||||
void __iomem *base;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "psc driver init\n");
|
||||
|
||||
#ifdef PSC_HAVE_NUMA
|
||||
node0mask = cpumask_of_node(0);
|
||||
#endif
|
||||
psc = devm_kzalloc(dev, sizeof(*psc), GFP_KERNEL);
|
||||
if (!psc)
|
||||
return -ENOMEM;
|
||||
|
||||
// first mailbox address (name mbox-regs)
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base)) {
|
||||
dev_err(dev, "ioremap failed\n");
|
||||
return PTR_ERR(base);
|
||||
}
|
||||
|
||||
psc->vm_chan_base = base;
|
||||
psc->dev = dev;
|
||||
|
||||
for (i = 0; i < MBOX_NUM; i++) {
|
||||
int irq;
|
||||
|
||||
irq = platform_get_irq(pdev, i);
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "Unable to get IRQ %d\n", irq);
|
||||
return irq;
|
||||
}
|
||||
ret = devm_request_irq(dev, irq, psc_mbox_rx_interrupt,
|
||||
IRQF_ONESHOT, dev_name(dev), &psc->chan[i]);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to acquire IRQ %d\n", irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef PSC_HAVE_NUMA
|
||||
irq_set_affinity(irq, node0mask);
|
||||
#endif
|
||||
|
||||
psc->chan[i].con_priv = &psc->vm_chan[i];
|
||||
psc->vm_chan[i].parent = psc;
|
||||
psc->vm_chan[i].irq = irq;
|
||||
psc->vm_chan[i].base = base + (MBOX_REG_OFFSET * i);
|
||||
dev_dbg(dev, "vm_chan[%d].base:%p, irq:%d\n",
|
||||
i, psc->vm_chan[i].base, irq);
|
||||
}
|
||||
psc->mbox.dev = dev;
|
||||
psc->mbox.chans = &psc->chan[0]; /* mbox_request_channel(cl,0) returns this one */
|
||||
psc->mbox.num_chans = MBOX_NUM;
|
||||
psc->mbox.ops = &psc_mbox_ops;
|
||||
/* drive txdone by mailbox client ACK with tx_block set to false */
|
||||
psc->mbox.txdone_irq = false;
|
||||
psc->mbox.txdone_poll = false;
|
||||
|
||||
platform_set_drvdata(pdev, psc);
|
||||
|
||||
/* adds to global kernel mbox_cons */
|
||||
ret = mbox_controller_register(&psc->mbox);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register mailboxes %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
psc_debugfs_create(pdev, &psc->mbox);
|
||||
dev_info(dev, "init done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra234_psc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct psc_mbox *psc = platform_get_drvdata(pdev);
|
||||
|
||||
psc_debugfs_remove(pdev);
|
||||
|
||||
mbox_controller_unregister(&psc->mbox);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id tegra234_psc_match[] = {
|
||||
{ .compatible = "nvidia,tegra234-psc", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tegra234_psc_match);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id tegra23x_psc_acpi_match[] = {
|
||||
{
|
||||
.id = "NVDA2003",
|
||||
.driver_data = 0
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, tegra23x_psc_acpi_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver tegra234_psc_driver = {
|
||||
.probe = tegra234_psc_probe,
|
||||
.remove = tegra234_psc_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "tegra23x-psc",
|
||||
.of_match_table = of_match_ptr(tegra234_psc_match),
|
||||
#ifdef CONFIG_ACPI
|
||||
.acpi_match_table = ACPI_PTR(tegra23x_psc_acpi_match),
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
struct mbox_chan *psc_mbox_request_channel0(struct mbox_controller *mbox, struct mbox_client *cl)
|
||||
{
|
||||
struct device *dev = cl->dev;
|
||||
struct mbox_chan *chan;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (!dev) {
|
||||
pr_err("%s: device is NULL\n", __func__);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
chan = &mbox->chans[0];
|
||||
if (IS_ERR(chan)) {
|
||||
dev_err(dev, "%s: channel [0] has an error\n", __func__);
|
||||
return chan;
|
||||
}
|
||||
|
||||
if (chan->cl || !try_module_get(mbox->dev->driver->owner)) {
|
||||
dev_err(dev, "%s: mailbox not free\n", __func__);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
spin_lock_irqsave(&chan->lock, flags);
|
||||
chan->msg_free = 0;
|
||||
chan->msg_count = 0;
|
||||
chan->active_req = NULL;
|
||||
chan->cl = cl;
|
||||
init_completion(&chan->tx_complete);
|
||||
|
||||
if (chan->txdone_method == TXDONE_BY_POLL && cl->knows_txdone)
|
||||
chan->txdone_method |= TXDONE_BY_ACK;
|
||||
|
||||
chan->mbox = mbox;
|
||||
|
||||
spin_unlock_irqrestore(&chan->lock, flags);
|
||||
|
||||
if (chan->mbox->ops == NULL || chan->mbox->ops->startup == NULL) {
|
||||
pr_err("%s%d invalid ops\n", __func__, __LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = chan->mbox->ops->startup(chan);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to startup the channel (%d)\n", ret);
|
||||
mbox_free_channel(chan);
|
||||
chan = ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
module_platform_driver(tegra234_psc_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Tegra PSC driver");
|
||||
MODULE_AUTHOR("dpu@nvidia.com");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Reference in New Issue
Block a user