diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index 1a2908e5..4eead8b0 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -29,4 +29,5 @@ endif obj-m += mc-hwpm.o obj-m += mc-utils/ obj-m += dce/ +obj-m += psc/ obj-m += rtcpu/ diff --git a/drivers/platform/tegra/psc/Makefile b/drivers/platform/tegra/psc/Makefile new file mode 100644 index 00000000..ee0ea460 --- /dev/null +++ b/drivers/platform/tegra/psc/Makefile @@ -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 diff --git a/drivers/platform/tegra/psc/psc_debug.c b/drivers/platform/tegra/psc/psc_debug.c new file mode 100644 index 00000000..4174e614 --- /dev/null +++ b/drivers/platform/tegra/psc/psc_debug.c @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/drivers/platform/tegra/psc/tegra23x_psc.h b/drivers/platform/tegra/psc/tegra23x_psc.h new file mode 100644 index 00000000..cacc9b05 --- /dev/null +++ b/drivers/platform/tegra/psc/tegra23x_psc.h @@ -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 diff --git a/drivers/platform/tegra/psc/tegra23x_psc_mailbox.c b/drivers/platform/tegra/psc/tegra23x_psc_mailbox.c new file mode 100644 index 00000000..f9375daf --- /dev/null +++ b/drivers/platform/tegra/psc/tegra23x_psc_mailbox.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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");