From patchwork Sun Dec 30 16:49:36 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roland Stigge X-Patchwork-Id: 1919851 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id C7E8B402E1 for ; Sun, 30 Dec 2012 16:54:59 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TpM6j-0000CQ-1r; Sun, 30 Dec 2012 16:51:29 +0000 Received: from mail.work-microwave.de ([62.245.205.51] helo=work-microwave.de) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TpM5b-0008Qc-2f for linux-arm-kernel@lists.infradead.org; Sun, 30 Dec 2012 16:50:25 +0000 Received: from rst-pc1.lan.work-microwave.de ([192.168.11.78]) (authenticated bits=0) by mail.work-microwave.de with ESMTP id qBUGo2w5023249 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Sun, 30 Dec 2012 17:50:03 +0100 Received: by rst-pc1.lan.work-microwave.de (Postfix, from userid 1000) id 21519AE07F; Sun, 30 Dec 2012 17:50:02 +0100 (CET) From: Roland Stigge To: gregkh@linuxfoundation.org, grant.likely@secretlab.ca, linus.walleij@linaro.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, w.sang@pengutronix.de, jbe@pengutronix.de, plagnioj@jcrosoft.com, highguy@gmail.com, broonie@opensource.wolfsonmicro.com, daniel-gl@gmx.net, rmallon@gmail.com, sr@denx.de, wg@grandegger.com, tru@work-microwave.de, mark.rutland@arm.com Subject: [PATCH 3/6 v13] gpio: Add userland device interface to block GPIO Date: Sun, 30 Dec 2012 17:49:36 +0100 Message-Id: <1356886179-13535-4-git-send-email-stigge@antcom.de> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1356886179-13535-1-git-send-email-stigge@antcom.de> References: <1356886179-13535-1-git-send-email-stigge@antcom.de> X-FEAS-SYSTEM-WL: rst@work-microwave.de, 192.168.11.78 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20121230_115019_629656_AD8DC6F8 X-CRM114-Status: GOOD ( 18.68 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Roland Stigge X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org This patch adds a character device interface to the block GPIO system. Signed-off-by: Roland Stigge --- Documentation/ABI/testing/dev-gpioblock | 34 ++++ drivers/gpio/gpiolib.c | 225 +++++++++++++++++++++++++++++++- include/linux/gpio.h | 13 + 3 files changed, 271 insertions(+), 1 deletion(-) --- /dev/null +++ linux-2.6/Documentation/ABI/testing/dev-gpioblock @@ -0,0 +1,34 @@ +What: /dev/ +Date: Nov 2012 +KernelVersion: 3.7 +Contact: Roland Stigge +Description: The /dev/ character device node provides userspace + access to GPIO blocks, named exactly as the block, e.g. + /dev/block0. + + Reading: + When reading sizeof(unsigned long) bytes from the device, the + current state of the block, masked by the current mask (see + below) can be obtained as a word. When the device is opened + with O_NONBLOCK, read() always returns with data immediately, + otherwise it blocks until data is available, see IRQ handling + below. + + Writing: + By writing sizeof(unsigned long) bytes to the device, the + current state of the block can be set. This operation is + masked by the current mask (see below). + + IRQ handling: + When one or more IRQs in the block are IRQ capable, you can + poll() on the device to check/wait for this IRQ. If no IRQ + is available, poll() returns -ENOSYS and userspace needs to + (busy) poll itself if necessary. + + Setting the mask (default: all bits set): + By doing an ioctl(fd, 0, &mask) with an unsigned long mask, the + current mask for read and write operations on this gpio block + can be set. + + See also Documentation/gpio.txt for an explanation of block + GPIO. --- linux-2.6.orig/drivers/gpio/gpiolib.c +++ linux-2.6/drivers/gpio/gpiolib.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #define CREATE_TRACE_POINTS #include @@ -2243,6 +2245,207 @@ struct gpio_block *gpio_block_find_by_na } EXPORT_SYMBOL_GPL(gpio_block_find_by_name); +static struct gpio_block *gpio_block_find_by_minor(int minor) +{ + struct gpio_block *i; + + list_for_each_entry(i, &gpio_block_list, list) + if (i->miscdev.minor == minor) + return i; + return NULL; +} + +static bool gpio_block_is_irq_duplicate(struct gpio_block *block, int index) +{ + int irq = gpio_to_irq(block->gpio[index]); + int i; + + for (i = 0; i < index; i++) + if (gpio_to_irq(block->gpio[i]) == irq) + return true; + return false; +} + +static irqreturn_t gpio_block_irq_handler(int irq, void *dev) +{ + struct gpio_block *block = dev; + + wake_up_interruptible(&block->wait_queue); + block->got_int = true; + + return IRQ_HANDLED; +} + +static int gpio_block_fop_open(struct inode *in, struct file *f) +{ + int i; + struct gpio_block *block = gpio_block_find_by_minor(MINOR(in->i_rdev)); + int status; + int irq; + + if (!block) + return -ENOENT; + + block->irq_controlled = false; + block->got_int = false; + spin_lock_init(&block->lock); + init_waitqueue_head(&block->wait_queue); + f->private_data = block; + + for (i = 0; i < block->ngpio; i++) { + status = gpio_request(block->gpio[i], block->name); + if (status) + goto err1; + + irq = gpio_to_irq(block->gpio[i]); + if (irq >= 0 && + !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags) && + !gpio_block_is_irq_duplicate(block, i)) { + status = request_irq(irq, gpio_block_irq_handler, + IRQF_SHARED, + block->name, block); + if (status) + goto err2; + + block->irq_controlled = true; + } + } + + return 0; + +err1: + while (i > 0) { + i--; + + irq = gpio_to_irq(block->gpio[i]); + if (irq >= 0 && + !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags) && + !gpio_block_is_irq_duplicate(block, i)) + free_irq(irq, block); +err2: + gpio_free(block->gpio[i]); + } + return status; +} + +static int gpio_block_fop_release(struct inode *in, struct file *f) +{ + int i; + struct gpio_block *block = (struct gpio_block *)f->private_data; + + for (i = 0; i < block->ngpio; i++) { + int irq = gpio_to_irq(block->gpio[i]); + + if (irq >= 0 && + !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags) && + !gpio_block_is_irq_duplicate(block, i)) + free_irq(irq, block); + + gpio_free(block->gpio[i]); + } + + return 0; +} + +static int got_int(struct gpio_block *block) +{ + unsigned long flags; + int result; + + spin_lock_irqsave(&block->lock, flags); + result = block->got_int; + spin_unlock_irqrestore(&block->lock, flags); + + return result; +} + +static ssize_t gpio_block_fop_read(struct file *f, char __user *buf, size_t n, + loff_t *offset) +{ + struct gpio_block *block = (struct gpio_block *)f->private_data; + int err; + unsigned long flags; + + if (block->irq_controlled) { + if (!(f->f_flags & O_NONBLOCK)) + wait_event_interruptible(block->wait_queue, + got_int(block)); + spin_lock_irqsave(&block->lock, flags); + block->got_int = 0; + spin_unlock_irqrestore(&block->lock, flags); + } + + if (n >= sizeof(unsigned long)) { + unsigned long values = gpio_block_get(block, block->cur_mask); + + err = put_user(values, (unsigned long __user *)buf); + if (err) + return err; + + return sizeof(unsigned long); + } + return 0; +} + +static ssize_t gpio_block_fop_write(struct file *f, const char __user *buf, + size_t n, loff_t *offset) +{ + struct gpio_block *block = (struct gpio_block *)f->private_data; + int err; + + if (n >= sizeof(unsigned long)) { + unsigned long values; + + err = get_user(values, (unsigned long __user *)buf); + if (err) + return err; + if (gpio_block_is_output(block)) + gpio_block_set(block, block->cur_mask, values); + else + return -EPERM; + return sizeof(unsigned long); + } + return 0; +} + +static long gpio_block_fop_ioctl(struct file *f, unsigned int cmd, + unsigned long arg) +{ + struct gpio_block *block = (struct gpio_block *)f->private_data; + unsigned long __user *x = (unsigned long __user *)arg; + + if (cmd == 0) + return get_user(block->cur_mask, x); + + return -EINVAL; +} + +static unsigned int gpio_block_fop_poll(struct file *f, + struct poll_table_struct *pt) +{ + struct gpio_block *block = (struct gpio_block *)f->private_data; + + if (!block->irq_controlled) + return -ENOSYS; + + if (!got_int(block)) + poll_wait(f, &block->wait_queue, pt); + + if (got_int(block)) + return POLLIN; + + return 0; +} + +static const struct file_operations gpio_block_fops = { + .open = gpio_block_fop_open, + .release = gpio_block_fop_release, + .read = gpio_block_fop_read, + .write = gpio_block_fop_write, + .unlocked_ioctl = gpio_block_fop_ioctl, + .poll = gpio_block_fop_poll, +}; + int gpio_block_register(struct gpio_block *block) { int ret; @@ -2253,12 +2456,31 @@ int gpio_block_register(struct gpio_bloc list_add(&block->list, &gpio_block_list); ret = gpio_block_export(block); - if (ret) + + /* + * ret == ENOSYS is the case where GPIO_SYSFS is deactivated. In this + * case, we can continue safely anyway, since we can provide the device + * interface. + */ + if (ret && ret != -ENOSYS) goto err1; + block->miscdev.name = block->name; + block->miscdev.nodename = block->name; + block->miscdev.minor = MISC_DYNAMIC_MINOR; + block->miscdev.fops = &gpio_block_fops; + block->miscdev.mode = S_IWUSR | S_IRUGO; + + ret = misc_register(&block->miscdev); + if (ret) + goto err2; + return 0; +err2: + gpio_block_unexport(block); err1: list_del(&block->list); + return ret; } EXPORT_SYMBOL_GPL(gpio_block_register); @@ -2271,6 +2493,7 @@ void gpio_block_unregister(struct gpio_b if (i == block) { list_del(&i->list); gpio_block_unexport(block); + misc_deregister(&block->miscdev); break; } } --- linux-2.6.orig/include/linux/gpio.h +++ linux-2.6/include/linux/gpio.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include /* see Documentation/gpio.txt */ @@ -89,6 +92,11 @@ struct gpio_block_chip { * @gpio: list of gpios in this block * @list: global list of blocks, maintained by gpiolib * @cur_mask: currently used gpio mask used by userspace API + * @miscdev: userspace API: device + * @wait_queue: userspace API: wait queue waiting for IRQ + * @irq_controlled: userspace API: flag: using IRQ or not + * @got_int: userspace API: change detection via IRQ + * @lock: userspace API: spinlock for IRQ manipulated data */ struct gpio_block { struct list_head gbc_list; @@ -99,6 +107,11 @@ struct gpio_block { struct list_head list; unsigned long cur_mask; + struct miscdevice miscdev; + wait_queue_head_t wait_queue; + bool irq_controlled; + bool got_int; + spinlock_t lock; }; #ifdef CONFIG_GENERIC_GPIO