This patch adds sysfs support to the block GPIO API.

Signed-off-by: Roland Stigge <sti...@antcom.de>

---
 Documentation/ABI/testing/sysfs-gpio |   20 ++
 drivers/gpio/gpiolib.c               |  252 ++++++++++++++++++++++++++++++++++-
 include/asm-generic/gpio.h           |   11 +
 include/linux/gpio.h                 |   15 ++
 4 files changed, 297 insertions(+), 1 deletion(-)

--- linux-2.6.orig/Documentation/ABI/testing/sysfs-gpio
+++ linux-2.6/Documentation/ABI/testing/sysfs-gpio
@@ -25,3 +25,23 @@ Description:
            /label ... (r/o) descriptive, not necessarily unique
            /ngpio ... (r/o) number of GPIOs; numbered N to N + (ngpio - 1)
 
+What:          /sys/class/gpioblock/
+Date:          October 2012
+KernelVersion: 3.7
+Contact:       Roland Stigge <sti...@antcom.de>
+Description:
+
+  Block GPIO devices are visible in sysfs as soon as they are registered
+  (e.g. via devicetree definition). For actual I/O use, their "exported"
+  boolean attribute must be set to "1". Then, the attribute "values" is
+  created and at the same time, the GPIOs in the block are requested for
+  exclusive use by sysfs.
+
+    /sys/class/gpioblock
+       /BLOCKNAME ... for each GPIO block name
+           /ngpio ... (r/o) number of GPIOs in this group
+           /exported ... sysfs export state of this group (0, 1)
+           /value ... current value as 32 or 64 bit integer in hex
+                       (only available if /exported is 1)
+           /mask ... current mask as 32 or 64 bit integer in hex
+                       (only available if /exported is 1)
--- linux-2.6.orig/drivers/gpio/gpiolib.c
+++ linux-2.6/drivers/gpio/gpiolib.c
@@ -224,7 +224,8 @@ static bool gpio_block_is_output(struct
        int i;
 
        for (i = 0; i < block->ngpio; i++)
-               if (!test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags))
+               if ((block->cur_mask & BIT(i)) &&
+                   !test_bit(FLAG_IS_OUT, &gpio_desc[block->gpio[i]].flags))
                        return false;
        return true;
 }
@@ -1020,6 +1021,240 @@ static void gpiochip_unexport(struct gpi
                                chip->label, status);
 }
 
+static ssize_t gpio_block_ngpio_show(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       const struct gpio_block *block = dev_get_drvdata(dev);
+
+       return sprintf(buf, "%u\n", block->ngpio);
+}
+
+static ssize_t gpio_block_value_show(struct device *dev,
+                                    struct device_attribute *attr, char *buf)
+{
+       const struct gpio_block *block = dev_get_drvdata(dev);
+
+       return sprintf(buf, sizeof(unsigned long) == 4 ? "0x%08lx\n" :
+                      "0x%016lx\n", gpio_block_get(block, block->cur_mask));
+}
+
+static ssize_t gpio_block_value_store(struct device *dev,
+                                     struct device_attribute *attr,
+                                     const char *buf, size_t size)
+{
+       ssize_t                 status;
+       struct gpio_block       *block = dev_get_drvdata(dev);
+       unsigned long           value;
+
+       status = kstrtoul(buf, 0, &value);
+       if (status == 0) {
+               mutex_lock(&sysfs_lock);
+               if (gpio_block_is_output(block)) {
+                       gpio_block_set(block, block->cur_mask, value);
+                       status = size;
+               } else {
+                       status = -EPERM;
+               }
+               mutex_unlock(&sysfs_lock);
+       }
+       return status;
+}
+
+static struct device_attribute
+dev_attr_block_value = __ATTR(value, S_IWUSR | S_IRUGO, gpio_block_value_show,
+                             gpio_block_value_store);
+
+static ssize_t gpio_block_mask_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       const struct gpio_block *block = dev_get_drvdata(dev);
+
+       return sprintf(buf, sizeof(unsigned long) == 4 ? "0x%08lx\n" :
+                      "0x%016lx\n", block->cur_mask);
+}
+
+static ssize_t gpio_block_mask_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t size)
+{
+       ssize_t                 status;
+       struct gpio_block       *block = dev_get_drvdata(dev);
+       unsigned long           mask;
+
+       status = kstrtoul(buf, 0, &mask);
+       if (status == 0) {
+               block->cur_mask = mask;
+               status = size;
+       }
+       return status;
+}
+
+static struct device_attribute
+dev_attr_block_mask = __ATTR(mask, S_IWUSR | S_IRUGO, gpio_block_mask_show,
+                            gpio_block_mask_store);
+
+static struct class gpio_block_class;
+
+static int gpio_block_value_is_exported(struct gpio_block *block)
+{
+       struct device           *dev;
+       struct sysfs_dirent     *sd = NULL;
+
+       mutex_lock(&sysfs_lock);
+       dev = class_find_device(&gpio_block_class, NULL, block, match_export);
+       if (!dev)
+               goto out;
+
+       sd = sysfs_get_dirent(dev->kobj.sd, NULL, "value");
+
+out:
+       mutex_unlock(&sysfs_lock);
+       return !!sd;
+}
+
+static ssize_t gpio_block_exported_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct gpio_block       *block = dev_get_drvdata(dev);
+
+       return sprintf(buf, "%u\n", gpio_block_value_is_exported(block));
+}
+
+static int gpio_block_value_export(struct gpio_block *block)
+{
+       struct device   *dev;
+       int             status;
+       int             i;
+
+       mutex_lock(&sysfs_lock);
+
+       for (i = 0; i < block->ngpio; i++) {
+               status = gpio_request(block->gpio[i], "sysfs");
+               if (status)
+                       goto out1;
+       }
+
+       dev = class_find_device(&gpio_block_class, NULL, block, match_export);
+       if (!dev) {
+               status = -ENODEV;
+               goto out1;
+       }
+
+       status = device_create_file(dev, &dev_attr_block_value);
+       if (status)
+               goto out1;
+
+       status = device_create_file(dev, &dev_attr_block_mask);
+       if (status)
+               goto out2;
+
+       mutex_unlock(&sysfs_lock);
+       return 0;
+
+out2:
+       device_remove_file(dev, &dev_attr_block_value);
+out1:
+       while (--i >= 0)
+               gpio_free(block->gpio[i]);
+
+       mutex_unlock(&sysfs_lock);
+       return status;
+}
+
+static int gpio_block_value_unexport(struct gpio_block *block)
+{
+       struct device   *dev;
+       int             i;
+
+       dev = class_find_device(&gpio_block_class, NULL, block, match_export);
+       if (!dev)
+               return -ENODEV;
+
+       for (i = 0; i < block->ngpio; i++)
+               gpio_free(block->gpio[i]);
+
+       device_remove_file(dev, &dev_attr_block_value);
+       device_remove_file(dev, &dev_attr_block_mask);
+
+       return 0;
+}
+
+static ssize_t gpio_block_exported_store(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf, size_t size)
+{
+       long    value;
+       int     status;
+       struct  gpio_block *block = dev_get_drvdata(dev);
+       int     exported = gpio_block_value_is_exported(block);
+
+       status = kstrtoul(buf, 0, &value);
+       if (status < 0)
+               goto err;
+
+       if (value != exported) {
+               if (value)
+                       status = gpio_block_value_export(block);
+               else
+                       status = gpio_block_value_unexport(block);
+               if (!status)
+                       status = size;
+       } else {
+               status = size;
+       }
+err:
+       return status;
+}
+
+static struct device_attribute gpio_block_attrs[] = {
+       __ATTR(exported, S_IWUSR | S_IRUGO, gpio_block_exported_show,
+              gpio_block_exported_store),
+       __ATTR(ngpio, S_IRUGO, gpio_block_ngpio_show, NULL),
+       __ATTR_NULL,
+};
+
+static struct class gpio_block_class = {
+       .name =         "gpioblock",
+       .owner =        THIS_MODULE,
+
+       .dev_attrs =  gpio_block_attrs,
+};
+
+int gpio_block_export(struct gpio_block *block)
+{
+       int             status = 0;
+       struct device   *dev;
+
+       /* can't export until sysfs is available ... */
+       if (!gpio_block_class.p) {
+               pr_debug("%s: called too early!\n", __func__);
+               return -ENOENT;
+       }
+
+       mutex_lock(&sysfs_lock);
+       dev = device_create(&gpio_block_class, NULL, MKDEV(0, 0), block,
+                           block->name);
+       if (IS_ERR(dev))
+               status = PTR_ERR(dev);
+       mutex_unlock(&sysfs_lock);
+
+       return status;
+}
+EXPORT_SYMBOL_GPL(gpio_block_export);
+
+void gpio_block_unexport(struct gpio_block *block)
+{
+       struct device *dev;
+
+       mutex_lock(&sysfs_lock);
+       dev = class_find_device(&gpio_block_class, NULL, block, match_export);
+       if (dev)
+               device_unregister(dev);
+       mutex_unlock(&sysfs_lock);
+}
+EXPORT_SYMBOL_GPL(gpio_block_unexport);
+
 static int __init gpiolib_sysfs_init(void)
 {
        int             status;
@@ -1030,6 +1265,10 @@ static int __init gpiolib_sysfs_init(voi
        if (status < 0)
                return status;
 
+       status = class_register(&gpio_block_class);
+       if (status < 0)
+               return status;
+
        /* Scan and register the gpio_chips which registered very
         * early (e.g. before the class_register above was called).
         *
@@ -1843,6 +2082,7 @@ struct gpio_block *gpio_block_create(uns
 
        block->name = name;
        block->ngpio = size;
+       block->cur_mask = ~0;
        INIT_LIST_HEAD(&block->gbc_list);
        block->gpio = kzalloc(sizeof(*block->gpio) * size, GFP_KERNEL);
        if (!block->gpio)
@@ -2005,12 +2245,21 @@ EXPORT_SYMBOL_GPL(gpio_block_find_by_nam
 
 int gpio_block_register(struct gpio_block *block)
 {
+       int ret;
+
        if (gpio_block_find_by_name(block->name))
                return -EBUSY;
 
        list_add(&block->list, &gpio_block_list);
 
+       ret = gpio_block_export(block);
+       if (ret)
+               goto err1;
+
        return 0;
+err1:
+       list_del(&block->list);
+       return ret;
 }
 EXPORT_SYMBOL_GPL(gpio_block_register);
 
@@ -2021,6 +2270,7 @@ void gpio_block_unregister(struct gpio_b
        list_for_each_entry(i, &gpio_block_list, list)
                if (i == block) {
                        list_del(&i->list);
+                       gpio_block_unexport(block);
                        break;
                }
 }
--- linux-2.6.orig/include/asm-generic/gpio.h
+++ linux-2.6/include/asm-generic/gpio.h
@@ -226,6 +226,8 @@ extern int gpio_export_link(struct devic
                        unsigned gpio);
 extern int gpio_sysfs_set_active_low(unsigned gpio, int value);
 extern void gpio_unexport(unsigned gpio);
+extern int gpio_block_export(struct gpio_block *block);
+extern void gpio_block_unexport(struct gpio_block *block);
 
 #endif /* CONFIG_GPIO_SYSFS */
 
@@ -285,6 +287,15 @@ static inline int gpio_sysfs_set_active_
 static inline void gpio_unexport(unsigned gpio)
 {
 }
+
+static inline int gpio_block_export(struct gpio_block *block)
+{
+       return -ENOSYS;
+}
+
+static inline void gpio_block_unexport(struct gpio_block *block)
+{
+}
 #endif /* CONFIG_GPIO_SYSFS */
 
 #ifdef CONFIG_PINCTRL
--- linux-2.6.orig/include/linux/gpio.h
+++ linux-2.6/include/linux/gpio.h
@@ -88,6 +88,7 @@ struct gpio_block_chip {
  * @ngpio:      number of gpios in this block
  * @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
  */
 struct gpio_block {
        struct list_head        gbc_list;
@@ -97,6 +98,7 @@ struct gpio_block {
        unsigned                *gpio;
 
        struct list_head        list;
+       unsigned long           cur_mask;
 };
 
 #ifdef CONFIG_GENERIC_GPIO
@@ -314,6 +316,19 @@ static inline void gpio_unexport(unsigne
        WARN_ON(1);
 }
 
+static inline int gpio_block_export(struct gpio_block *block)
+{
+       /* GPIO block can never have been requested or set as {in,out}put */
+       WARN_ON(1);
+       return -EINVAL;
+}
+
+static inline void gpio_block_unexport(struct gpio_block *block)
+{
+       /* GPIO block can never have been exported */
+       WARN_ON(1);
+}
+
 static inline int gpio_to_irq(unsigned gpio)
 {
        /* GPIO can never have been requested or set as input */
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to