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:
Liang Cheng
2023-03-28 16:35:06 -05:00
committed by mobile promotions
parent ba066d3e58
commit b4b748f294
5 changed files with 809 additions and 0 deletions

View File

@@ -29,4 +29,5 @@ endif
obj-m += mc-hwpm.o
obj-m += mc-utils/
obj-m += dce/
obj-m += psc/
obj-m += rtcpu/

View 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

View 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);
}

View 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

View 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");