aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/broadcom
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/broadcom')
-rw-r--r--drivers/char/broadcom/Kconfig33
-rw-r--r--drivers/char/broadcom/Makefile3
-rw-r--r--drivers/char/broadcom/bcm2835_smi_dev.c408
-rw-r--r--drivers/char/broadcom/vc_mem.c635
-rw-r--r--drivers/char/broadcom/vcio.c187
5 files changed, 1266 insertions, 0 deletions
diff --git a/drivers/char/broadcom/Kconfig b/drivers/char/broadcom/Kconfig
new file mode 100644
index 000000000000..29d880d47282
--- /dev/null
+++ b/drivers/char/broadcom/Kconfig
@@ -0,0 +1,33 @@
+#
+# Broadcom char driver config
+#
+
+menuconfig BRCM_CHAR_DRIVERS
+ bool "Broadcom Char Drivers"
+ help
+ Broadcom's char drivers
+
+if BRCM_CHAR_DRIVERS
+
+config BCM2708_VCMEM
+ bool "Videocore Memory"
+ default y
+ help
+ Helper for videocore memory access and total size allocation.
+
+config BCM_VCIO
+ tristate "Mailbox userspace access"
+ depends on BCM2835_MBOX
+ help
+ Gives access to the mailbox property channel from userspace.
+
+endif
+
+config BCM2835_SMI_DEV
+ tristate "Character device driver for BCM2835 Secondary Memory Interface"
+ depends on BCM2835_SMI
+ default m
+ help
+ This driver provides a character device interface (ioctl + read/write) to
+ Broadcom's Secondary Memory interface. The low-level functionality is provided
+ by the SMI driver itself.
diff --git a/drivers/char/broadcom/Makefile b/drivers/char/broadcom/Makefile
new file mode 100644
index 000000000000..2ae3e9d411e9
--- /dev/null
+++ b/drivers/char/broadcom/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_BCM2708_VCMEM) += vc_mem.o
+obj-$(CONFIG_BCM_VCIO) += vcio.o
+obj-$(CONFIG_BCM2835_SMI_DEV) += bcm2835_smi_dev.o
diff --git a/drivers/char/broadcom/bcm2835_smi_dev.c b/drivers/char/broadcom/bcm2835_smi_dev.c
new file mode 100644
index 000000000000..ae0c0d24fad9
--- /dev/null
+++ b/drivers/char/broadcom/bcm2835_smi_dev.c
@@ -0,0 +1,408 @@
+/**
+ * Character device driver for Broadcom Secondary Memory Interface
+ *
+ * Written by Luke Wren <[email protected]>
+ * Copyright (c) 2015, Raspberry Pi (Trading) Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+
+#include <linux/broadcom/bcm2835_smi.h>
+
+#define DEVICE_NAME "bcm2835-smi-dev"
+#define DRIVER_NAME "smi-dev-bcm2835"
+#define DEVICE_MINOR 0
+
+static struct cdev bcm2835_smi_cdev;
+static dev_t bcm2835_smi_devid;
+static struct class *bcm2835_smi_class;
+static struct device *bcm2835_smi_dev;
+
+struct bcm2835_smi_dev_instance {
+ struct device *dev;
+};
+
+static struct bcm2835_smi_instance *smi_inst;
+static struct bcm2835_smi_dev_instance *inst;
+
+static const char *const ioctl_names[] = {
+ "READ_SETTINGS",
+ "WRITE_SETTINGS",
+ "ADDRESS"
+};
+
+/****************************************************************************
+*
+* SMI chardev file ops
+*
+***************************************************************************/
+static long
+bcm2835_smi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ long ret = 0;
+
+ dev_info(inst->dev, "serving ioctl...");
+
+ switch (cmd) {
+ case BCM2835_SMI_IOC_GET_SETTINGS:{
+ struct smi_settings *settings;
+
+ dev_info(inst->dev, "Reading SMI settings to user.");
+ settings = bcm2835_smi_get_settings_from_regs(smi_inst);
+ if (copy_to_user((void *)arg, settings,
+ sizeof(struct smi_settings)))
+ dev_err(inst->dev, "settings copy failed.");
+ break;
+ }
+ case BCM2835_SMI_IOC_WRITE_SETTINGS:{
+ struct smi_settings *settings;
+
+ dev_info(inst->dev, "Setting user's SMI settings.");
+ settings = bcm2835_smi_get_settings_from_regs(smi_inst);
+ if (copy_from_user(settings, (void *)arg,
+ sizeof(struct smi_settings)))
+ dev_err(inst->dev, "settings copy failed.");
+ else
+ bcm2835_smi_set_regs_from_settings(smi_inst);
+ break;
+ }
+ case BCM2835_SMI_IOC_ADDRESS:
+ dev_info(inst->dev, "SMI address set: 0x%02x", (int)arg);
+ bcm2835_smi_set_address(smi_inst, arg);
+ break;
+ default:
+ dev_err(inst->dev, "invalid ioctl cmd: %d", cmd);
+ ret = -ENOTTY;
+ break;
+ }
+
+ return ret;
+}
+
+static int bcm2835_smi_open(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode);
+
+ dev_dbg(inst->dev, "SMI device opened.");
+
+ if (dev != DEVICE_MINOR) {
+ dev_err(inst->dev,
+ "bcm2835_smi_release: Unknown minor device: %d",
+ dev);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int bcm2835_smi_release(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode);
+
+ if (dev != DEVICE_MINOR) {
+ dev_err(inst->dev,
+ "bcm2835_smi_release: Unknown minor device %d", dev);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static ssize_t dma_bounce_user(
+ enum dma_transfer_direction dma_dir,
+ char __user *user_ptr,
+ size_t count,
+ struct bcm2835_smi_bounce_info *bounce)
+{
+ int chunk_size;
+ int chunk_no = 0;
+ int count_left = count;
+
+ while (count_left) {
+ int rv;
+ void *buf;
+
+ /* Wait for current chunk to complete: */
+ if (down_timeout(&bounce->callback_sem,
+ msecs_to_jiffies(1000))) {
+ dev_err(inst->dev, "DMA bounce timed out");
+ count -= (count_left);
+ break;
+ }
+
+ if (bounce->callback_sem.count >= DMA_BOUNCE_BUFFER_COUNT - 1)
+ dev_err(inst->dev, "WARNING: Ring buffer overflow");
+ chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ?
+ DMA_BOUNCE_BUFFER_SIZE : count_left;
+ buf = bounce->buffer[chunk_no % DMA_BOUNCE_BUFFER_COUNT];
+ if (dma_dir == DMA_DEV_TO_MEM)
+ rv = copy_to_user(user_ptr, buf, chunk_size);
+ else
+ rv = copy_from_user(buf, user_ptr, chunk_size);
+ if (rv)
+ dev_err(inst->dev, "copy_*_user() failed!: %d", rv);
+ user_ptr += chunk_size;
+ count_left -= chunk_size;
+ chunk_no++;
+ }
+ return count;
+}
+
+static ssize_t
+bcm2835_read_file(struct file *f, char __user *user_ptr,
+ size_t count, loff_t *offs)
+{
+ int odd_bytes;
+ size_t count_check;
+
+ dev_dbg(inst->dev, "User reading %zu bytes from SMI.", count);
+ /* We don't want to DMA a number of bytes % 4 != 0 (32 bit FIFO) */
+ if (count > DMA_THRESHOLD_BYTES)
+ odd_bytes = count & 0x3;
+ else
+ odd_bytes = count;
+ count -= odd_bytes;
+ count_check = count;
+ if (count) {
+ struct bcm2835_smi_bounce_info *bounce;
+
+ count = bcm2835_smi_user_dma(smi_inst,
+ DMA_DEV_TO_MEM, user_ptr, count,
+ &bounce);
+ if (count)
+ count = dma_bounce_user(DMA_DEV_TO_MEM, user_ptr,
+ count, bounce);
+ }
+ if (odd_bytes && (count == count_check)) {
+ /* Read from FIFO directly if not using DMA */
+ uint8_t buf[DMA_THRESHOLD_BYTES];
+ unsigned long bytes_not_transferred;
+
+ bcm2835_smi_read_buf(smi_inst, buf, odd_bytes);
+ bytes_not_transferred = copy_to_user(user_ptr + count, buf, odd_bytes);
+ if (bytes_not_transferred)
+ dev_err(inst->dev, "copy_to_user() failed.");
+ count += odd_bytes - bytes_not_transferred;
+ }
+ return count;
+}
+
+static ssize_t
+bcm2835_write_file(struct file *f, const char __user *user_ptr,
+ size_t count, loff_t *offs)
+{
+ int odd_bytes;
+ size_t count_check;
+
+ dev_dbg(inst->dev, "User writing %zu bytes to SMI.", count);
+ if (count > DMA_THRESHOLD_BYTES)
+ odd_bytes = count & 0x3;
+ else
+ odd_bytes = count;
+ count -= odd_bytes;
+ count_check = count;
+ if (count) {
+ struct bcm2835_smi_bounce_info *bounce;
+
+ count = bcm2835_smi_user_dma(smi_inst,
+ DMA_MEM_TO_DEV, (char __user *)user_ptr, count,
+ &bounce);
+ if (count)
+ count = dma_bounce_user(DMA_MEM_TO_DEV,
+ (char __user *)user_ptr,
+ count, bounce);
+ }
+ if (odd_bytes && (count == count_check)) {
+ uint8_t buf[DMA_THRESHOLD_BYTES];
+ unsigned long bytes_not_transferred;
+
+ bytes_not_transferred = copy_from_user(buf, user_ptr + count, odd_bytes);
+ if (bytes_not_transferred)
+ dev_err(inst->dev, "copy_from_user() failed.");
+ else
+ bcm2835_smi_write_buf(smi_inst, buf, odd_bytes);
+ count += odd_bytes - bytes_not_transferred;
+ }
+ return count;
+}
+
+static const struct file_operations
+bcm2835_smi_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = bcm2835_smi_ioctl,
+ .open = bcm2835_smi_open,
+ .release = bcm2835_smi_release,
+ .read = bcm2835_read_file,
+ .write = bcm2835_write_file,
+};
+
+
+/****************************************************************************
+*
+* bcm2835_smi_probe - called when the driver is loaded.
+*
+***************************************************************************/
+
+static int bcm2835_smi_dev_probe(struct platform_device *pdev)
+{
+ int err;
+ void *ptr_err;
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node, *smi_node;
+
+ if (!node) {
+ dev_err(dev, "No device tree node supplied!");
+ return -EINVAL;
+ }
+
+ smi_node = of_parse_phandle(node, "smi_handle", 0);
+
+ if (!smi_node) {
+ dev_err(dev, "No such property: smi_handle");
+ return -ENXIO;
+ }
+
+ smi_inst = bcm2835_smi_get(smi_node);
+
+ if (!smi_inst)
+ return -EPROBE_DEFER;
+
+ /* Allocate buffers and instance data */
+
+ inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL);
+
+ if (!inst)
+ return -ENOMEM;
+
+ inst->dev = dev;
+
+ /* Create character device entries */
+
+ err = alloc_chrdev_region(&bcm2835_smi_devid,
+ DEVICE_MINOR, 1, DEVICE_NAME);
+ if (err != 0) {
+ dev_err(inst->dev, "unable to allocate device number");
+ return -ENOMEM;
+ }
+ cdev_init(&bcm2835_smi_cdev, &bcm2835_smi_fops);
+ bcm2835_smi_cdev.owner = THIS_MODULE;
+ err = cdev_add(&bcm2835_smi_cdev, bcm2835_smi_devid, 1);
+ if (err != 0) {
+ dev_err(inst->dev, "unable to register device");
+ err = -ENOMEM;
+ goto failed_cdev_add;
+ }
+
+ /* Create sysfs entries */
+
+ bcm2835_smi_class = class_create(DEVICE_NAME);
+ ptr_err = bcm2835_smi_class;
+ if (IS_ERR(ptr_err))
+ goto failed_class_create;
+
+ bcm2835_smi_dev = device_create(bcm2835_smi_class, NULL,
+ bcm2835_smi_devid, NULL,
+ "smi");
+ ptr_err = bcm2835_smi_dev;
+ if (IS_ERR(ptr_err))
+ goto failed_device_create;
+
+ dev_info(inst->dev, "initialised");
+
+ return 0;
+
+failed_device_create:
+ class_destroy(bcm2835_smi_class);
+failed_class_create:
+ cdev_del(&bcm2835_smi_cdev);
+ err = PTR_ERR(ptr_err);
+failed_cdev_add:
+ unregister_chrdev_region(bcm2835_smi_devid, 1);
+ dev_err(dev, "could not load bcm2835_smi_dev");
+ return err;
+}
+
+/****************************************************************************
+*
+* bcm2835_smi_remove - called when the driver is unloaded.
+*
+***************************************************************************/
+
+static void bcm2835_smi_dev_remove(struct platform_device *pdev)
+{
+ device_destroy(bcm2835_smi_class, bcm2835_smi_devid);
+ class_destroy(bcm2835_smi_class);
+ cdev_del(&bcm2835_smi_cdev);
+ unregister_chrdev_region(bcm2835_smi_devid, 1);
+
+ dev_info(inst->dev, "SMI character dev removed - OK");
+}
+
+/****************************************************************************
+*
+* Register the driver with device tree
+*
+***************************************************************************/
+
+static const struct of_device_id bcm2835_smi_dev_of_match[] = {
+ {.compatible = "brcm,bcm2835-smi-dev",},
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_smi_dev_of_match);
+
+static struct platform_driver bcm2835_smi_dev_driver = {
+ .probe = bcm2835_smi_dev_probe,
+ .remove = bcm2835_smi_dev_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = bcm2835_smi_dev_of_match,
+ },
+};
+
+module_platform_driver(bcm2835_smi_dev_driver);
+
+MODULE_ALIAS("platform:smi-dev-bcm2835");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(
+ "Character device driver for BCM2835's secondary memory interface");
+MODULE_AUTHOR("Luke Wren <[email protected]>");
diff --git a/drivers/char/broadcom/vc_mem.c b/drivers/char/broadcom/vc_mem.c
new file mode 100644
index 000000000000..4b7faf084e0c
--- /dev/null
+++ b/drivers/char/broadcom/vc_mem.c
@@ -0,0 +1,635 @@
+/*
+ * Copyright 2010 - 2011 Broadcom Corporation. All rights reserved.
+ *
+ * Unless you and Broadcom execute a separate written software license
+ * agreement governing use of this software, this software is licensed to you
+ * under the terms of the GNU General Public License version 2, available at
+ * http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
+ *
+ * Notwithstanding the above, under no circumstances may you combine this
+ * software in any way with any other Broadcom software provided under a
+ * license other than the GPL, without Broadcom's express prior written
+ * consent.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/dma-mapping.h>
+#include <linux/broadcom/vc_mem.h>
+#include <linux/compat.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/dma-bcm2708.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define DRIVER_NAME "vc-mem"
+
+/* N.B. These use a different magic value for compatibility with bmc7208_fb */
+#define VC_MEM_IOC_DMACOPY _IOW('z', 0x22, struct vc_mem_dmacopy)
+#define VC_MEM_IOC_DMACOPY32 _IOW('z', 0x22, struct vc_mem_dmacopy32)
+
+/* address with no aliases */
+#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000)
+/* cache coherent but non-allocating in L1 and L2 */
+#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000)
+
+/* Device (/dev) related variables */
+static dev_t vc_mem_devnum;
+static struct class *vc_mem_class;
+static struct cdev vc_mem_cdev;
+static int vc_mem_inited;
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *vc_mem_debugfs_entry;
+#endif
+
+struct vc_mem_dmacopy {
+ void *dst;
+ __u32 src;
+ __u32 length;
+};
+
+#ifdef CONFIG_COMPAT
+struct vc_mem_dmacopy32 {
+ compat_uptr_t dst;
+ __u32 src;
+ __u32 length;
+};
+#endif
+
+/*
+ * Videocore memory addresses and size
+ *
+ * Drivers that wish to know the videocore memory addresses and sizes should
+ * use these variables instead of the MM_IO_BASE and MM_ADDR_IO defines in
+ * headers. This allows the other drivers to not be tied down to a a certain
+ * address/size at compile time.
+ *
+ * In the future, the goal is to have the videocore memory virtual address and
+ * size be calculated at boot time rather than at compile time. The decision of
+ * where the videocore memory resides and its size would be in the hands of the
+ * bootloader (and/or kernel). When that happens, the values of these variables
+ * would be calculated and assigned in the init function.
+ */
+/* In the 2835 VC in mapped above ARM, but ARM has full access to VC space */
+unsigned long mm_vc_mem_phys_addr;
+EXPORT_SYMBOL(mm_vc_mem_phys_addr);
+unsigned int mm_vc_mem_size;
+EXPORT_SYMBOL(mm_vc_mem_size);
+unsigned int mm_vc_mem_base;
+EXPORT_SYMBOL(mm_vc_mem_base);
+
+static uint phys_addr;
+static uint mem_size;
+static uint mem_base;
+
+struct vc_mem_dma {
+ struct device *dev;
+ int dma_chan;
+ int dma_irq;
+ void __iomem *dma_chan_base;
+ wait_queue_head_t dma_waitq;
+ void *cb_base; /* DMA control blocks */
+ dma_addr_t cb_handle;
+};
+
+struct { u32 base, length; } gpu_mem;
+static struct mutex dma_mutex;
+static struct vc_mem_dma vc_mem_dma;
+
+static int
+vc_mem_open(struct inode *inode, struct file *file)
+{
+ (void)inode;
+
+ pr_debug("%s: called file = 0x%p\n", __func__, file);
+
+ return 0;
+}
+
+static int
+vc_mem_release(struct inode *inode, struct file *file)
+{
+ (void)inode;
+
+ pr_debug("%s: called file = 0x%p\n", __func__, file);
+
+ return 0;
+}
+
+static void
+vc_mem_get_size(void)
+{
+}
+
+static void
+vc_mem_get_base(void)
+{
+}
+
+int
+vc_mem_get_current_size(void)
+{
+ return mm_vc_mem_size;
+}
+EXPORT_SYMBOL_GPL(vc_mem_get_current_size);
+
+static int
+vc_mem_dma_init(void)
+{
+ struct vc_mem_dma *vcdma = &vc_mem_dma;
+ struct platform_device *pdev;
+ struct device_node *fwnode;
+ struct rpi_firmware *fw;
+ struct device *dev;
+ u32 revision;
+ int rc;
+
+ if (vcdma->dev)
+ return 0;
+
+ fwnode = of_find_node_by_path("/system");
+ rc = of_property_read_u32(fwnode, "linux,revision", &revision);
+ revision = (revision >> 12) & 0xf;
+ if (revision != 1 && revision != 2) {
+ /* Only BCM2709 and BCM2710 may have logs where the ARMs
+ * can't see them.
+ */
+ return -ENXIO;
+ }
+
+ fwnode = rpi_firmware_find_node();
+ if (!fwnode)
+ return -ENXIO;
+
+ pdev = of_find_device_by_node(fwnode);
+ dev = &pdev->dev;
+
+ rc = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (rc)
+ return rc;
+
+ fw = rpi_firmware_get(fwnode);
+ if (!fw)
+ return -ENXIO;
+ rc = rpi_firmware_property(fw, RPI_FIRMWARE_GET_VC_MEMORY,
+ &gpu_mem, sizeof(gpu_mem));
+ if (rc)
+ return rc;
+
+ gpu_mem.base = INTALIAS_NORMAL(gpu_mem.base);
+
+ if (!gpu_mem.base || !gpu_mem.length) {
+ dev_err(dev, "%s: unable to determine gpu memory (%x,%x)\n",
+ __func__, gpu_mem.base, gpu_mem.length);
+ return -EFAULT;
+ }
+
+ vcdma->cb_base = dma_alloc_wc(dev, SZ_4K, &vcdma->cb_handle, GFP_KERNEL);
+ if (!vcdma->cb_base) {
+ dev_err(dev, "failed to allocate DMA CBs\n");
+ return -ENOMEM;
+ }
+
+ rc = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK,
+ &vcdma->dma_chan_base,
+ &vcdma->dma_irq);
+ if (rc < 0) {
+ dev_err(dev, "failed to allocate a DMA channel\n");
+ goto free_cb;
+ }
+
+ vcdma->dma_chan = rc;
+
+ init_waitqueue_head(&vcdma->dma_waitq);
+
+ vcdma->dev = dev;
+
+ return 0;
+
+free_cb:
+ dma_free_wc(dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle);
+
+ return rc;
+}
+
+static void
+vc_mem_dma_uninit(void)
+{
+ struct vc_mem_dma *vcdma = &vc_mem_dma;
+
+ if (vcdma->dev) {
+ bcm_dma_chan_free(vcdma->dma_chan);
+ dma_free_wc(vcdma->dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle);
+ vcdma->dev = NULL;
+ }
+}
+
+static int dma_memcpy(struct vc_mem_dma *vcdma, dma_addr_t dst, dma_addr_t src,
+ int size)
+{
+ struct bcm2708_dma_cb *cb = vcdma->cb_base;
+ int burst_size = (vcdma->dma_chan == 0) ? 8 : 2;
+
+ cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH |
+ BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH |
+ BCM2708_DMA_D_INC;
+ cb->dst = dst;
+ cb->src = src;
+ cb->length = size;
+ cb->stride = 0;
+ cb->pad[0] = 0;
+ cb->pad[1] = 0;
+ cb->next = 0;
+
+ bcm_dma_start(vcdma->dma_chan_base, vcdma->cb_handle);
+ bcm_dma_wait_idle(vcdma->dma_chan_base);
+
+ return 0;
+}
+
+static long vc_mem_copy(struct vc_mem_dmacopy *ioparam)
+{
+ struct vc_mem_dma *vcdma = &vc_mem_dma;
+ size_t size = PAGE_SIZE;
+ const u32 dma_xfer_chunk = 256;
+ u32 *buf = NULL;
+ dma_addr_t bus_addr;
+ long rc = 0;
+ size_t offset;
+
+ /* restrict this to root user */
+ if (!uid_eq(current_euid(), GLOBAL_ROOT_UID))
+ return -EFAULT;
+
+ if (mutex_lock_interruptible(&dma_mutex))
+ return -EINTR;
+
+ rc = vc_mem_dma_init();
+ if (rc)
+ goto out;
+
+ vcdma = &vc_mem_dma;
+
+ if (INTALIAS_NORMAL(ioparam->src) < gpu_mem.base ||
+ INTALIAS_NORMAL(ioparam->src) >= gpu_mem.base + gpu_mem.length) {
+ pr_err("%s: invalid memory access %x (%x-%x)", __func__,
+ INTALIAS_NORMAL(ioparam->src), gpu_mem.base,
+ gpu_mem.base + gpu_mem.length);
+ rc = -EFAULT;
+ goto out;
+ }
+
+ buf = dma_alloc_coherent(vcdma->dev, PAGE_ALIGN(size), &bus_addr,
+ GFP_ATOMIC);
+ if (!buf) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ for (offset = 0; offset < ioparam->length; offset += size) {
+ size_t remaining = ioparam->length - offset;
+ size_t s = min(size, remaining);
+ u8 *p = (u8 *)((uintptr_t)ioparam->src + offset);
+ u8 *q = (u8 *)ioparam->dst + offset;
+
+ rc = dma_memcpy(vcdma, bus_addr,
+ INTALIAS_L1L2_NONALLOCATING((u32)(uintptr_t)p),
+ (s + dma_xfer_chunk - 1) & ~(dma_xfer_chunk - 1));
+ if (rc) {
+ dev_err(vcdma->dev, "dma_memcpy failed\n");
+ break;
+ }
+ if (copy_to_user(q, buf, s) != 0) {
+ pr_err("%s: copy_to_user failed\n", __func__);
+ rc = -EFAULT;
+ break;
+ }
+ }
+
+out:
+ if (buf)
+ dma_free_coherent(vcdma->dev, PAGE_ALIGN(size), buf,
+ bus_addr);
+
+ mutex_unlock(&dma_mutex);
+
+ return rc;
+}
+
+static long
+vc_mem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+
+ (void) cmd;
+ (void) arg;
+
+ pr_debug("%s: called file = 0x%p, cmd %08x\n", __func__, file, cmd);
+
+ switch (cmd) {
+ case VC_MEM_IOC_MEM_PHYS_ADDR:
+ {
+ pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR=0x%p\n",
+ __func__, (void *)mm_vc_mem_phys_addr);
+
+ if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr,
+ sizeof(mm_vc_mem_phys_addr))) {
+ rc = -EFAULT;
+ }
+ break;
+ }
+ case VC_MEM_IOC_MEM_SIZE:
+ {
+ /* Get the videocore memory size first */
+ vc_mem_get_size();
+
+ pr_debug("%s: VC_MEM_IOC_MEM_SIZE=%x\n", __func__,
+ mm_vc_mem_size);
+
+ if (copy_to_user((void *)arg, &mm_vc_mem_size,
+ sizeof(mm_vc_mem_size))) {
+ rc = -EFAULT;
+ }
+ break;
+ }
+ case VC_MEM_IOC_MEM_BASE:
+ {
+ /* Get the videocore memory base */
+ vc_mem_get_base();
+
+ pr_debug("%s: VC_MEM_IOC_MEM_BASE=%x\n", __func__,
+ mm_vc_mem_base);
+
+ if (copy_to_user((void *)arg, &mm_vc_mem_base,
+ sizeof(mm_vc_mem_base))) {
+ rc = -EFAULT;
+ }
+ break;
+ }
+ case VC_MEM_IOC_MEM_LOAD:
+ {
+ /* Get the videocore memory base */
+ vc_mem_get_base();
+
+ pr_debug("%s: VC_MEM_IOC_MEM_LOAD=%x\n", __func__,
+ mm_vc_mem_base);
+
+ if (copy_to_user((void *)arg, &mm_vc_mem_base,
+ sizeof(mm_vc_mem_base))) {
+ rc = -EFAULT;
+ }
+ break;
+ }
+ case VC_MEM_IOC_DMACOPY:
+ {
+ struct vc_mem_dmacopy ioparam;
+ /* Get the parameter data.
+ */
+ if (copy_from_user
+ (&ioparam, (void *)arg, sizeof(ioparam))) {
+ pr_err("%s: copy_from_user failed\n", __func__);
+ rc = -EFAULT;
+ break;
+ }
+
+ rc = vc_mem_copy(&ioparam);
+ break;
+ }
+ default:
+ {
+ return -ENOTTY;
+ }
+ }
+ pr_debug("%s: file = 0x%p returning %d\n", __func__, file, rc);
+
+ return rc;
+}
+
+#ifdef CONFIG_COMPAT
+static long
+vc_mem_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+
+ switch (cmd) {
+ case VC_MEM_IOC_MEM_PHYS_ADDR32:
+ pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR32=0x%p\n",
+ __func__, (void *)mm_vc_mem_phys_addr);
+
+ /* This isn't correct, but will cover us for now as
+ * VideoCore is 32bit only.
+ */
+ if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr,
+ sizeof(compat_ulong_t)))
+ rc = -EFAULT;
+
+ break;
+
+ case VC_MEM_IOC_DMACOPY32:
+ {
+ struct vc_mem_dmacopy32 param32;
+ struct vc_mem_dmacopy param;
+ /* Get the parameter data.
+ */
+ if (copy_from_user(&param32, (void *)arg, sizeof(param32))) {
+ pr_err("%s: copy_from_user failed\n", __func__);
+ rc = -EFAULT;
+ break;
+ }
+ param.dst = compat_ptr(param32.dst);
+ param.src = param32.src;
+ param.length = param32.length;
+ rc = vc_mem_copy(&param);
+ break;
+ }
+
+ default:
+ rc = vc_mem_ioctl(file, cmd, arg);
+ break;
+ }
+
+ return rc;
+}
+#endif
+
+static int
+vc_mem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int rc = 0;
+ unsigned long length = vma->vm_end - vma->vm_start;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ pr_debug("%s: vm_start = 0x%08lx vm_end = 0x%08lx vm_pgoff = 0x%08lx\n",
+ __func__, (long)vma->vm_start, (long)vma->vm_end,
+ (long)vma->vm_pgoff);
+
+ if (offset + length > mm_vc_mem_size) {
+ pr_err("%s: length %ld is too big\n", __func__, length);
+ return -EINVAL;
+ }
+ /* Do not cache the memory map */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ rc = remap_pfn_range(vma, vma->vm_start,
+ (mm_vc_mem_phys_addr >> PAGE_SHIFT) +
+ vma->vm_pgoff, length, vma->vm_page_prot);
+ if (rc)
+ pr_err("%s: remap_pfn_range failed (rc=%d)\n", __func__, rc);
+
+ return rc;
+}
+
+/* File Operations for the driver. */
+static const struct file_operations vc_mem_fops = {
+ .owner = THIS_MODULE,
+ .open = vc_mem_open,
+ .release = vc_mem_release,
+ .unlocked_ioctl = vc_mem_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = vc_mem_compat_ioctl,
+#endif
+ .mmap = vc_mem_mmap,
+};
+
+#ifdef CONFIG_DEBUG_FS
+static void vc_mem_debugfs_deinit(void)
+{
+ debugfs_remove_recursive(vc_mem_debugfs_entry);
+ vc_mem_debugfs_entry = NULL;
+}
+
+
+static int vc_mem_debugfs_init(
+ struct device *dev)
+{
+ vc_mem_debugfs_entry = debugfs_create_dir(DRIVER_NAME, NULL);
+ if (!vc_mem_debugfs_entry) {
+ dev_warn(dev, "could not create debugfs entry\n");
+ return -EFAULT;
+ }
+
+ debugfs_create_x32("vc_mem_phys_addr",
+ 0444,
+ vc_mem_debugfs_entry,
+ (u32 *)&mm_vc_mem_phys_addr);
+ debugfs_create_x32("vc_mem_size",
+ 0444,
+ vc_mem_debugfs_entry,
+ (u32 *)&mm_vc_mem_size);
+ debugfs_create_x32("vc_mem_base",
+ 0444,
+ vc_mem_debugfs_entry,
+ (u32 *)&mm_vc_mem_base);
+
+ return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+/* Module load/unload functions */
+
+static int __init
+vc_mem_init(void)
+{
+ int rc = -EFAULT;
+ struct device *dev;
+
+ pr_debug("%s: called\n", __func__);
+
+ mm_vc_mem_phys_addr = phys_addr;
+ mm_vc_mem_size = mem_size;
+ mm_vc_mem_base = mem_base;
+
+ vc_mem_get_size();
+
+ pr_info("vc-mem: phys_addr:0x%08lx mem_base=0x%08x mem_size:0x%08x(%u MiB)\n",
+ mm_vc_mem_phys_addr, mm_vc_mem_base, mm_vc_mem_size,
+ mm_vc_mem_size / (1024 * 1024));
+
+ rc = alloc_chrdev_region(&vc_mem_devnum, 0, 1, DRIVER_NAME);
+ if (rc < 0) {
+ pr_err("%s: alloc_chrdev_region failed (rc=%d)\n",
+ __func__, rc);
+ goto out_err;
+ }
+
+ cdev_init(&vc_mem_cdev, &vc_mem_fops);
+ rc = cdev_add(&vc_mem_cdev, vc_mem_devnum, 1);
+ if (rc) {
+ pr_err("%s: cdev_add failed (rc=%d)\n", __func__, rc);
+ goto out_unregister;
+ }
+
+ vc_mem_class = class_create(DRIVER_NAME);
+ if (IS_ERR(vc_mem_class)) {
+ rc = PTR_ERR(vc_mem_class);
+ pr_err("%s: class_create failed (rc=%d)\n", __func__, rc);
+ goto out_cdev_del;
+ }
+
+ dev = device_create(vc_mem_class, NULL, vc_mem_devnum, NULL,
+ DRIVER_NAME);
+ if (IS_ERR(dev)) {
+ rc = PTR_ERR(dev);
+ pr_err("%s: device_create failed (rc=%d)\n", __func__, rc);
+ goto out_class_destroy;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ /* don't fail if the debug entries cannot be created */
+ vc_mem_debugfs_init(dev);
+#endif
+
+ mutex_init(&dma_mutex);
+ vc_mem_inited = 1;
+ return 0;
+
+out_class_destroy:
+ class_destroy(vc_mem_class);
+ vc_mem_class = NULL;
+
+out_cdev_del:
+ cdev_del(&vc_mem_cdev);
+
+out_unregister:
+ unregister_chrdev_region(vc_mem_devnum, 1);
+
+out_err:
+ return -1;
+}
+
+static void __exit
+vc_mem_exit(void)
+{
+ pr_debug("%s: called\n", __func__);
+
+ vc_mem_dma_uninit();
+ if (vc_mem_inited) {
+#ifdef CONFIG_DEBUG_FS
+ vc_mem_debugfs_deinit();
+#endif
+ device_destroy(vc_mem_class, vc_mem_devnum);
+ class_destroy(vc_mem_class);
+ cdev_del(&vc_mem_cdev);
+ unregister_chrdev_region(vc_mem_devnum, 1);
+ vc_mem_inited = 0;
+ }
+}
+
+module_init(vc_mem_init);
+module_exit(vc_mem_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Broadcom Corporation");
+
+module_param(phys_addr, uint, 0644);
+module_param(mem_size, uint, 0644);
+module_param(mem_base, uint, 0644);
diff --git a/drivers/char/broadcom/vcio.c b/drivers/char/broadcom/vcio.c
new file mode 100644
index 000000000000..9db2408c781a
--- /dev/null
+++ b/drivers/char/broadcom/vcio.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2010 Broadcom
+ * Copyright (C) 2015 Noralf Trønnes
+ * Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/compat.h>
+#include <linux/miscdevice.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define MODULE_NAME "vcio"
+#define VCIO_IOC_MAGIC 100
+#define IOCTL_MBOX_PROPERTY _IOWR(VCIO_IOC_MAGIC, 0, char *)
+#ifdef CONFIG_COMPAT
+#define IOCTL_MBOX_PROPERTY32 _IOWR(VCIO_IOC_MAGIC, 0, compat_uptr_t)
+#endif
+
+struct vcio_data {
+ struct rpi_firmware *fw;
+ struct miscdevice misc_dev;
+};
+
+static int vcio_user_property_list(struct vcio_data *vcio, void *user)
+{
+ u32 *buf, size;
+ int ret;
+
+ /* The first 32-bit is the size of the buffer */
+ if (copy_from_user(&size, user, sizeof(size)))
+ return -EFAULT;
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ if (copy_from_user(buf, user, size)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ /* Strip off protocol encapsulation */
+ ret = rpi_firmware_property_list(vcio->fw, &buf[2], size - 12);
+ if (ret) {
+ kfree(buf);
+ return ret;
+ }
+
+ buf[1] = RPI_FIRMWARE_STATUS_SUCCESS;
+ if (copy_to_user(user, buf, size))
+ ret = -EFAULT;
+
+ kfree(buf);
+
+ return ret;
+}
+
+static int vcio_device_open(struct inode *inode, struct file *file)
+{
+ try_module_get(THIS_MODULE);
+
+ return 0;
+}
+
+static int vcio_device_release(struct inode *inode, struct file *file)
+{
+ module_put(THIS_MODULE);
+
+ return 0;
+}
+
+static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num,
+ unsigned long ioctl_param)
+{
+ struct vcio_data *vcio = container_of(file->private_data,
+ struct vcio_data, misc_dev);
+
+ switch (ioctl_num) {
+ case IOCTL_MBOX_PROPERTY:
+ return vcio_user_property_list(vcio, (void *)ioctl_param);
+ default:
+ pr_err("unknown ioctl: %x\n", ioctl_num);
+ return -EINVAL;
+ }
+}
+
+#ifdef CONFIG_COMPAT
+static long vcio_device_compat_ioctl(struct file *file, unsigned int ioctl_num,
+ unsigned long ioctl_param)
+{
+ struct vcio_data *vcio = container_of(file->private_data,
+ struct vcio_data, misc_dev);
+
+ switch (ioctl_num) {
+ case IOCTL_MBOX_PROPERTY32:
+ return vcio_user_property_list(vcio, compat_ptr(ioctl_param));
+ default:
+ pr_err("unknown ioctl: %x\n", ioctl_num);
+ return -EINVAL;
+ }
+}
+#endif
+
+const struct file_operations vcio_fops = {
+ .unlocked_ioctl = vcio_device_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = vcio_device_compat_ioctl,
+#endif
+ .open = vcio_device_open,
+ .release = vcio_device_release,
+};
+
+static int vcio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *fw_node;
+ struct rpi_firmware *fw;
+ struct vcio_data *vcio;
+
+ fw_node = of_get_parent(np);
+ if (!fw_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ fw = rpi_firmware_get(fw_node);
+ of_node_put(fw_node);
+ if (!fw)
+ return -EPROBE_DEFER;
+
+ vcio = devm_kzalloc(dev, sizeof(struct vcio_data), GFP_KERNEL);
+ if (!vcio)
+ return -ENOMEM;
+
+ vcio->fw = fw;
+ vcio->misc_dev.fops = &vcio_fops;
+ vcio->misc_dev.minor = MISC_DYNAMIC_MINOR;
+ vcio->misc_dev.name = "vcio";
+ vcio->misc_dev.parent = dev;
+
+ return misc_register(&vcio->misc_dev);
+}
+
+static void vcio_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ misc_deregister(dev_get_drvdata(dev));
+}
+
+static const struct of_device_id vcio_ids[] = {
+ { .compatible = "raspberrypi,vcio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, vcio_ids);
+
+static struct platform_driver vcio_driver = {
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(vcio_ids),
+ },
+ .probe = vcio_probe,
+ .remove = vcio_remove,
+};
+
+module_platform_driver(vcio_driver);
+
+MODULE_AUTHOR("Gray Girling");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_DESCRIPTION("Mailbox userspace access");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rpi-vcio");