diff --git a/MAINTAINERS b/MAINTAINERS index 47bf5496c67e..8c3b5813c3ec 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14727,3 +14727,7 @@ DWAV TOUCHSCREEN DRIVER M: codewalker@hardkernel.com> S: Maintained F: drivers/input/touchscreen/dwav-usb-mt.c + +HARDKERNEL S922D odroidn2 +M: Kevin Kim +F: drivers/char/aml-gpiomem.c diff --git a/arch/arm64/boot/dts/amlogic/mesong12_odroid_common.dtsi b/arch/arm64/boot/dts/amlogic/mesong12_odroid_common.dtsi index 6f4327b7dad3..a46f5a2ee91d 100644 --- a/arch/arm64/boot/dts/amlogic/mesong12_odroid_common.dtsi +++ b/arch/arm64/boot/dts/amlogic/mesong12_odroid_common.dtsi @@ -31,6 +31,12 @@ tsensor1 = &d_tsensor; }; + gpiomem { + compatible = "amlogic, gpiomem"; + reg = <0x0 0xff634000 0x0 0x1000>; + status = "okay"; + }; + codec_mm { compatible = "amlogic, codec, mm"; memory-region = <&codec_mm_cma &codec_mm_reserved>; diff --git a/arch/arm64/configs/odroidn2_defconfig b/arch/arm64/configs/odroidn2_defconfig index acb6fd0c44b5..cc29de820e66 100644 --- a/arch/arm64/configs/odroidn2_defconfig +++ b/arch/arm64/configs/odroidn2_defconfig @@ -2304,6 +2304,7 @@ CONFIG_HW_RANDOM_MESON=y # CONFIG_RAW_DRIVER is not set # CONFIG_TCG_TPM is not set # CONFIG_XILLYBUS is not set +CONFIG_AMLOGIC_GPIOMEM=y # # I2C support diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 8453a49471d7..41aa9fd193ef 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -593,5 +593,14 @@ config TILE_SROM source "drivers/char/xillybus/Kconfig" +config AMLOGIC_GPIOMEM + tristate "/dev/gpiomem rootless GPIO access via mmap() on the Amlogic" + default m + ---help--- + Provides users with root-free access to the GPIO registers + on Meson g12 platform. Calling mmap(/dev/gpiomem) will map the GPIO + register page to the user's pointer. This drvier can allow to access + gpio memory area in user account. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 6e6c244a66a0..65f5ad4895fa 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -60,3 +60,5 @@ js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o + +obj-$(CONFIG_AMLOGIC_GPIOMEM) += aml-gpiomem.o diff --git a/drivers/char/aml-gpiomem.c b/drivers/char/aml-gpiomem.c new file mode 100644 index 000000000000..566b07cd7ffd --- /dev/null +++ b/drivers/char/aml-gpiomem.c @@ -0,0 +1,235 @@ +/* + * linux/drivers/char/aml-gpiomem.c + * + * GPIO memory device driver + * + * Creates a chardev /dev/gpiomem which will provide user access to + * the Meson g12 GPIO registers when it is mmap()'d. + * No longer need root for user GPIO access, but without relaxing permissions + * on /dev/mem. + * + * Copyright (c) 2017 Hardkernel Co., Ltd. + * + * This driver is based on bcm2835-gpiomem.c in Raspberrypi's linux kernel 4.4: + * Written by Luke Wren + * 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 +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "aml-gpiomem" +#define DRIVER_NAME "gpiomem-aml" +#define DEVICE_MINOR 0 + +struct aml_gpiomem_instance { + unsigned long gpio_regs_phys; + struct device *dev; +}; + +static struct cdev aml_gpiomem_cdev; +static dev_t aml_gpiomem_devid; +static struct class *aml_gpiomem_class; +static struct device *aml_gpiomem_dev; +static struct aml_gpiomem_instance *inst; + +static int aml_gpiomem_open(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + + dev_info(inst->dev, "gpiomem device opened."); + + if (dev != DEVICE_MINOR) { + dev_err(inst->dev, "Unknown minor device: %d", dev); + ret = -ENXIO; + } + return ret; +} + +static int aml_gpiomem_release(struct inode *inode, struct file *file) +{ + int dev = iminor(inode); + int ret = 0; + + if (dev != DEVICE_MINOR) { + dev_err(inst->dev, "Unknown minor device %d", dev); + ret = -ENXIO; + } + return ret; +} + +static const struct vm_operations_struct aml_gpiomem_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys +#endif +}; + +static int aml_gpiomem_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long gpio_page = inst->gpio_regs_phys >> PAGE_SHIFT; + + vma->vm_page_prot = phys_mem_access_prot(file, gpio_page, + PAGE_SIZE, + vma->vm_page_prot); + + vma->vm_ops = &aml_gpiomem_vm_ops; + if (remap_pfn_range(vma, vma->vm_start, + gpio_page, + PAGE_SIZE, + vma->vm_page_prot)) { + return -EAGAIN; + } + + return 0; +} + +static const struct file_operations +aml_gpiomem_fops = { + .owner = THIS_MODULE, + .open = aml_gpiomem_open, + .release = aml_gpiomem_release, + .mmap = aml_gpiomem_mmap, +}; + +static int aml_gpiomem_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *dev = &pdev->dev; + struct resource *res = NULL; + + /* Allocate buffers and instance data */ + inst = kzalloc(sizeof(struct aml_gpiomem_instance), GFP_KERNEL); + if (!inst) { + err = -ENOMEM; + goto failed_inst_alloc; + } + + inst->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + inst->gpio_regs_phys = res->start; + } else { + dev_err(inst->dev, "failed to get IO resource"); + err = -ENOENT; + goto failed_get_resource; + } + + /* Create character device entries */ + err = alloc_chrdev_region(&aml_gpiomem_devid, + DEVICE_MINOR, 1, DEVICE_NAME); + if (err != 0) { + dev_err(inst->dev, "unable to allocate device number"); + goto failed_alloc_chrdev; + } + cdev_init(&aml_gpiomem_cdev, &aml_gpiomem_fops); + aml_gpiomem_cdev.owner = THIS_MODULE; + err = cdev_add(&aml_gpiomem_cdev, aml_gpiomem_devid, 1); + if (err != 0) { + dev_err(inst->dev, "unable to register device"); + goto failed_cdev_add; + } + + /* Create sysfs entries */ + aml_gpiomem_class = class_create(THIS_MODULE, DEVICE_NAME); + err = IS_ERR(aml_gpiomem_class); + if (err) + goto failed_class_create; + + aml_gpiomem_dev = device_create(aml_gpiomem_class, NULL, + aml_gpiomem_devid, NULL, + "gpiomem"); + err = IS_ERR(aml_gpiomem_dev); + if (err) + goto failed_device_create; + + dev_info(inst->dev, "Initialised: Registers at 0x%08lx", + inst->gpio_regs_phys); + + return 0; + +failed_device_create: + class_destroy(aml_gpiomem_class); +failed_class_create: + cdev_del(&aml_gpiomem_cdev); +failed_cdev_add: + unregister_chrdev_region(aml_gpiomem_devid, 1); +failed_alloc_chrdev: +failed_get_resource: + kfree(inst); +failed_inst_alloc: + dev_err(inst->dev, "could not load aml_gpiomem"); + return err; +} + +static int aml_gpiomem_remove(struct platform_device *pdev) +{ + struct device *dev = inst->dev; + + kfree(inst); + device_destroy(aml_gpiomem_class, aml_gpiomem_devid); + class_destroy(aml_gpiomem_class); + cdev_del(&aml_gpiomem_cdev); + unregister_chrdev_region(aml_gpiomem_devid, 1); + + dev_info(dev, "GPIO mem driver removed - OK"); + return 0; +} + +static const struct of_device_id aml_gpiomem_of_match[] = { + {.compatible = "amlogic, gpiomem",}, + { }, +}; +MODULE_DEVICE_TABLE(of, aml_gpiomem_of_match); + +static struct platform_driver aml_gpiomem_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = aml_gpiomem_of_match, + }, + .probe = aml_gpiomem_probe, + .remove = aml_gpiomem_remove, +}; + +module_platform_driver(aml_gpiomem_driver); + +MODULE_ALIAS("platform:gpiomem-aml"); +MODULE_DESCRIPTION("AMLogic gpiomem driver for accessing GPIO from userspace"); +MODULE_AUTHOR("Brian Kim "); +MODULE_LICENSE("GPL");