// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /** * 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; unsigned long len; 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. */ len = min(vma->vm_end - vma->vm_start, priv->mem_window_len); vma->vm_page_prot = phys_mem_access_prot(file, pages, len, vma->vm_page_prot); vma->vm_ops = &rpivid_mem_vm_ops; if (remap_pfn_range(vma, vma->vm_start, pages, 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; const struct of_device_id *id; struct device *dev = &pdev->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 + 1) - 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); if (IS_ERR(priv->class)) { err = PTR_ERR(priv->class); goto failed_class_create; } dev = device_create(priv->class, NULL, priv->devid, NULL, priv->name); if (IS_ERR(dev)) { err = PTR_ERR(dev); goto failed_device_create; } 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); failed_cdev_add: unregister_chrdev_region(priv->devid, 1); failed_alloc_chrdev: failed_get_resource: kfree(priv); failed_inst_alloc: dev_err(&pdev->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 ");