From dcf515d36ce574edd773c9c6321b6bbf9724e209 Mon Sep 17 00:00:00 2001 From: Jonathan Bell Date: Thu, 9 May 2019 14:30:37 +0100 Subject: [PATCH] drivers: char: add chardev for mmap'ing the RPiVid control registers Based on the gpiomem driver, allow mapping of the decoder register spaces such that userspace can access control/status registers. This driver is intended for use with a custom ffmpeg backend accelerator prior to a v4l2 driver being written. Signed-off-by: Jonathan Bell --- drivers/char/broadcom/Kconfig | 8 + drivers/char/broadcom/Makefile | 1 + drivers/char/broadcom/rpivid-mem.c | 286 +++++++++++++++++++++++++++++ drivers/mfd/bcm2835-pm.c | 12 +- drivers/soc/bcm/bcm2835-power.c | 6 +- include/linux/mfd/bcm2835-pm.h | 2 +- 6 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 drivers/char/broadcom/rpivid-mem.c --- a/drivers/char/broadcom/Kconfig +++ b/drivers/char/broadcom/Kconfig @@ -49,3 +49,11 @@ config BCM2835_SMI_DEV 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. + +config RPIVID_MEM + tristate "Character device driver for the Raspberry Pi RPIVid video decoder hardware" + default n + help + This driver provides a character device interface for memory-map operations + so userspace tools can access the control and status registers of the + Raspberry Pi RPiVid video decoder hardware. --- a/drivers/char/broadcom/Makefile +++ b/drivers/char/broadcom/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_BCM_VC_SM) += vc_sm obj-$(CONFIG_BCM2835_DEVGPIOMEM)+= bcm2835-gpiomem.o obj-$(CONFIG_BCM2835_SMI_DEV) += bcm2835_smi_dev.o +obj-$(CONFIG_RPIVID_MEM) += rpivid-mem.o --- /dev/null +++ b/drivers/char/broadcom/rpivid-mem.c @@ -0,0 +1,286 @@ +/** + * rpivid-mem.c - character device access to the RPiVid decoder registers + * + * Based on bcm2835-gpiomem.c. Provides IO memory access to the decoder + * register blocks such that ffmpeg plugins can access the hardware. + * + * Jonathan Bell + * Copyright (c) 2019, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "rpivid-mem" +#define DEVICE_MINOR 0 + +struct rpivid_mem_priv { + dev_t devid; + struct class *class; + struct cdev rpivid_mem_cdev; + unsigned long regs_phys; + unsigned long mem_window_len; + struct device *dev; + const char *name; +}; + +static int rpivid_mem_open(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + struct rpivid_mem_priv *priv; + if (dev != DEVICE_MINOR && dev != DEVICE_MINOR + 1) + ret = -ENXIO; + + priv = container_of(inode->i_cdev, struct rpivid_mem_priv, + rpivid_mem_cdev); + if (!priv) + return -EINVAL; + file->private_data = priv; + return ret; +} + +static int rpivid_mem_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + + if (dev != DEVICE_MINOR && dev != DEVICE_MINOR + 1) + ret = -ENXIO; + + return ret; +} + +static const struct vm_operations_struct rpivid_mem_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys +#endif +}; + +static int rpivid_mem_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct rpivid_mem_priv *priv; + unsigned long pages; + + priv = file->private_data; + pages = priv->regs_phys >> PAGE_SHIFT; + /* + * The address decode is far larger than the actual number of registers. + * Just map the whole lot in. + */ + vma->vm_page_prot = phys_mem_access_prot(file, pages, + priv->mem_window_len, + vma->vm_page_prot); + vma->vm_ops = &rpivid_mem_vm_ops; + if (remap_pfn_range(vma, vma->vm_start, + pages, + priv->mem_window_len, + vma->vm_page_prot)) { + return -EAGAIN; + } + return 0; +} + +static const struct file_operations +rpivid_mem_fops = { + .owner = THIS_MODULE, + .open = rpivid_mem_open, + .release = rpivid_mem_release, + .mmap = rpivid_mem_mmap, +}; + +static const struct of_device_id rpivid_mem_of_match[]; +static int rpivid_mem_probe(struct platform_device *pdev) +{ + int err; + void *ptr_err; + const struct of_device_id *id; + struct device *dev = &pdev->dev; + struct device *rpivid_mem_dev; + struct resource *ioresource; + struct rpivid_mem_priv *priv; + + + /* Allocate buffers and instance data */ + + priv = kzalloc(sizeof(struct rpivid_mem_priv), GFP_KERNEL); + + if (!priv) { + err = -ENOMEM; + goto failed_inst_alloc; + } + platform_set_drvdata(pdev, priv); + + priv->dev = dev; + id = of_match_device(rpivid_mem_of_match, dev); + if (!id) + return -EINVAL; + priv->name = id->data; + + ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (ioresource) { + priv->regs_phys = ioresource->start; + priv->mem_window_len = ioresource->end - ioresource->start; + } else { + dev_err(priv->dev, "failed to get IO resource"); + err = -ENOENT; + goto failed_get_resource; + } + + /* Create character device entries */ + + err = alloc_chrdev_region(&priv->devid, + DEVICE_MINOR, 2, priv->name); + if (err != 0) { + dev_err(priv->dev, "unable to allocate device number"); + goto failed_alloc_chrdev; + } + cdev_init(&priv->rpivid_mem_cdev, &rpivid_mem_fops); + priv->rpivid_mem_cdev.owner = THIS_MODULE; + err = cdev_add(&priv->rpivid_mem_cdev, priv->devid, 2); + if (err != 0) { + dev_err(priv->dev, "unable to register device"); + goto failed_cdev_add; + } + + /* Create sysfs entries */ + + priv->class = class_create(THIS_MODULE, priv->name); + ptr_err = priv->class; + if (IS_ERR(ptr_err)) + goto failed_class_create; + + rpivid_mem_dev = device_create(priv->class, NULL, + priv->devid, NULL, + priv->name); + ptr_err = rpivid_mem_dev; + if (IS_ERR(ptr_err)) + goto failed_device_create; + + /* Legacy alias */ + { + char *oldname = kstrdup(priv->name, GFP_KERNEL); + + oldname[1] = 'a'; + oldname[2] = 'r'; + oldname[3] = 'g'; + oldname[4] = 'o'; + oldname[5] = 'n'; + (void)device_create(priv->class, NULL, priv->devid + 1, NULL, + oldname + 1); + kfree(oldname); + } + + dev_info(priv->dev, "%s initialised: Registers at 0x%08lx length 0x%08lx", + priv->name, priv->regs_phys, priv->mem_window_len); + + return 0; + +failed_device_create: + class_destroy(priv->class); +failed_class_create: + cdev_del(&priv->rpivid_mem_cdev); + err = PTR_ERR(ptr_err); +failed_cdev_add: + unregister_chrdev_region(priv->devid, 1); +failed_alloc_chrdev: +failed_get_resource: + kfree(priv); +failed_inst_alloc: + dev_err(priv->dev, "could not load rpivid_mem"); + return err; +} + +static int rpivid_mem_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpivid_mem_priv *priv = platform_get_drvdata(pdev); + + device_destroy(priv->class, priv->devid); + class_destroy(priv->class); + cdev_del(&priv->rpivid_mem_cdev); + unregister_chrdev_region(priv->devid, 1); + kfree(priv); + + dev_info(dev, "%s driver removed - OK", priv->name); + return 0; +} + +static const struct of_device_id rpivid_mem_of_match[] = { + { + .compatible = "raspberrypi,rpivid-hevc-decoder", + .data = "rpivid-hevcmem", + }, + { + .compatible = "raspberrypi,rpivid-h264-decoder", + .data = "rpivid-h264mem", + }, + { + .compatible = "raspberrypi,rpivid-vp9-decoder", + .data = "rpivid-vp9mem", + }, + /* The "intc" is included as this block of hardware contains the + * "frame done" status flags. + */ + { + .compatible = "raspberrypi,rpivid-local-intc", + .data = "rpivid-intcmem", + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, rpivid_mem_of_match); + +static struct platform_driver rpivid_mem_driver = { + .probe = rpivid_mem_probe, + .remove = rpivid_mem_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = rpivid_mem_of_match, + }, +}; + +module_platform_driver(rpivid_mem_driver); + +MODULE_ALIAS("platform:rpivid-mem"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for accessing RPiVid decoder registers from userspace"); +MODULE_AUTHOR("Jonathan Bell "); --- a/drivers/mfd/bcm2835-pm.c +++ b/drivers/mfd/bcm2835-pm.c @@ -50,14 +50,14 @@ static int bcm2835_pm_probe(struct platf if (ret) return ret; - /* Map the ARGON ASB regs if present. */ + /* Map the RPiVid ASB regs if present. */ res = platform_get_resource(pdev, IORESOURCE_MEM, 2); if (res) { - pm->arg_asb = devm_ioremap_resource(dev, res); - if (IS_ERR(pm->arg_asb)) { - dev_err(dev, "Failed to map ARGON ASB: %ld\n", - PTR_ERR(pm->arg_asb)); - return PTR_ERR(pm->arg_asb); + pm->rpivid_asb = devm_ioremap_resource(dev, res); + if (IS_ERR(pm->rpivid_asb)) { + dev_err(dev, "Failed to map RPiVid ASB: %ld\n", + PTR_ERR(pm->rpivid_asb)); + return PTR_ERR(pm->rpivid_asb); } } --- a/drivers/soc/bcm/bcm2835-power.c +++ b/drivers/soc/bcm/bcm2835-power.c @@ -637,15 +637,15 @@ static int bcm2835_power_probe(struct pl power->base = pm->base; power->asb = pm->asb; - /* 2711 hack: the new ARGON ASB took over V3D, which is our + /* 2711 hack: the new RPiVid ASB took over V3D, which is our * only consumer of this driver so far. The old ASB seems to * still be present with ISP and H264 bits but no V3D, but I * don't know if that's real or not. The V3D is in the same * place in the new ASB as the old one, so just poke the new * one for now. */ - if (pm->arg_asb) { - power->asb = pm->arg_asb; + if (pm->rpivid_asb) { + power->asb = pm->rpivid_asb; power->is_2711 = true; } --- a/include/linux/mfd/bcm2835-pm.h +++ b/include/linux/mfd/bcm2835-pm.h @@ -9,7 +9,7 @@ struct bcm2835_pm { struct device *dev; void __iomem *base; void __iomem *asb; - void __iomem *arg_asb; + void __iomem *rpivid_asb; }; #endif /* BCM2835_MFD_PM_H */