Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 164820
b: refs/heads/master
c: ff77c35
h: refs/heads/master
v: v3
  • Loading branch information
Daniel Glöckner authored and Linus Torvalds committed Sep 23, 2009
1 parent bf11f34 commit 4a0622b
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 6 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: d120c17faeb391f5b4b99af8b1e190619934ecdd
refs/heads/master: ff77c352ae17768c61cfc36357f0a3904552f11c
1 change: 1 addition & 0 deletions trunk/Documentation/ABI/testing/sysfs-gpio
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Description:
/gpioN ... for each exported GPIO #N
/value ... always readable, writes fail for input GPIOs
/direction ... r/w as: in, out (default low); write: high, low
/edge ... r/w as: none, falling, rising, both
/gpiochipN ... for each gpiochip; #N is its first GPIO
/base ... (r/o) same as N
/label ... (r/o) descriptive, not necessarily unique
Expand Down
7 changes: 7 additions & 0 deletions trunk/Documentation/gpio.txt
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,13 @@ and have the following read/write attributes:
is configured as an output, this value may be written;
any nonzero value is treated as high.

"edge" ... reads as either "none", "rising", "falling", or
"both". Write these strings to select the signal edge(s)
that will make poll(2) on the "value" file return.

This file exists only if the pin can be configured as an
interrupt generating input pin.

GPIO controllers have paths like /sys/class/gpio/chipchip42/ (for the
controller implementing GPIOs starting at #42) and have the following
read-only attributes:
Expand Down
208 changes: 203 additions & 5 deletions trunk/drivers/gpio/gpiolib.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/gpio.h>
#include <linux/idr.h>


/* Optional implementation infrastructure for GPIO interfaces.
Expand Down Expand Up @@ -49,13 +51,29 @@ struct gpio_desc {
#define FLAG_RESERVED 2
#define FLAG_EXPORT 3 /* protected by sysfs_lock */
#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */
#define FLAG_TRIG_FALL 5 /* trigger on falling edge */
#define FLAG_TRIG_RISE 6 /* trigger on rising edge */

#define PDESC_ID_SHIFT 16 /* add new flags before this one */

#define GPIO_FLAGS_MASK ((1 << PDESC_ID_SHIFT) - 1)
#define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE))

#ifdef CONFIG_DEBUG_FS
const char *label;
#endif
};
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

#ifdef CONFIG_GPIO_SYSFS
struct poll_desc {
struct work_struct work;
struct sysfs_dirent *value_sd;
};

static struct idr pdesc_idr;
#endif

static inline void desc_set_label(struct gpio_desc *d, const char *label)
{
#ifdef CONFIG_DEBUG_FS
Expand Down Expand Up @@ -188,10 +206,10 @@ static DEFINE_MUTEX(sysfs_lock);
* /value
* * always readable, subject to hardware behavior
* * may be writable, as zero/nonzero
*
* REVISIT there will likely be an attribute for configuring async
* notifications, e.g. to specify polling interval or IRQ trigger type
* that would for example trigger a poll() on the "value".
* /edge
* * configures behavior of poll(2) on /value
* * available only if pin can generate IRQs on input
* * is read/write as "none", "falling", "rising", or "both"
*/

static ssize_t gpio_direction_show(struct device *dev,
Expand Down Expand Up @@ -288,6 +306,175 @@ static ssize_t gpio_value_store(struct device *dev,
static /*const*/ DEVICE_ATTR(value, 0644,
gpio_value_show, gpio_value_store);

static irqreturn_t gpio_sysfs_irq(int irq, void *priv)
{
struct work_struct *work = priv;

schedule_work(work);
return IRQ_HANDLED;
}

static void gpio_notify_sysfs(struct work_struct *work)
{
struct poll_desc *pdesc;

pdesc = container_of(work, struct poll_desc, work);
sysfs_notify_dirent(pdesc->value_sd);
}

static int gpio_setup_irq(struct gpio_desc *desc, struct device *dev,
unsigned long gpio_flags)
{
struct poll_desc *pdesc;
unsigned long irq_flags;
int ret, irq, id;

if ((desc->flags & GPIO_TRIGGER_MASK) == gpio_flags)
return 0;

irq = gpio_to_irq(desc - gpio_desc);
if (irq < 0)
return -EIO;

id = desc->flags >> PDESC_ID_SHIFT;
pdesc = idr_find(&pdesc_idr, id);
if (pdesc) {
free_irq(irq, &pdesc->work);
cancel_work_sync(&pdesc->work);
}

desc->flags &= ~GPIO_TRIGGER_MASK;

if (!gpio_flags) {
ret = 0;
goto free_sd;
}

irq_flags = IRQF_SHARED;
if (test_bit(FLAG_TRIG_FALL, &gpio_flags))
irq_flags |= IRQF_TRIGGER_FALLING;
if (test_bit(FLAG_TRIG_RISE, &gpio_flags))
irq_flags |= IRQF_TRIGGER_RISING;

if (!pdesc) {
pdesc = kmalloc(sizeof(*pdesc), GFP_KERNEL);
if (!pdesc) {
ret = -ENOMEM;
goto err_out;
}

do {
ret = -ENOMEM;
if (idr_pre_get(&pdesc_idr, GFP_KERNEL))
ret = idr_get_new_above(&pdesc_idr,
pdesc, 1, &id);
} while (ret == -EAGAIN);

if (ret)
goto free_mem;

desc->flags &= GPIO_FLAGS_MASK;
desc->flags |= (unsigned long)id << PDESC_ID_SHIFT;

if (desc->flags >> PDESC_ID_SHIFT != id) {
ret = -ERANGE;
goto free_id;
}

pdesc->value_sd = sysfs_get_dirent(dev->kobj.sd, "value");
if (!pdesc->value_sd) {
ret = -ENODEV;
goto free_id;
}
INIT_WORK(&pdesc->work, gpio_notify_sysfs);
}

ret = request_irq(irq, gpio_sysfs_irq, irq_flags,
"gpiolib", &pdesc->work);
if (ret)
goto free_sd;

desc->flags |= gpio_flags;
return 0;

free_sd:
sysfs_put(pdesc->value_sd);
free_id:
idr_remove(&pdesc_idr, id);
desc->flags &= GPIO_FLAGS_MASK;
free_mem:
kfree(pdesc);
err_out:
return ret;
}

static const struct {
const char *name;
unsigned long flags;
} trigger_types[] = {
{ "none", 0 },
{ "falling", BIT(FLAG_TRIG_FALL) },
{ "rising", BIT(FLAG_TRIG_RISE) },
{ "both", BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE) },
};

static ssize_t gpio_edge_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const struct gpio_desc *desc = dev_get_drvdata(dev);
ssize_t status;

mutex_lock(&sysfs_lock);

if (!test_bit(FLAG_EXPORT, &desc->flags))
status = -EIO;
else {
int i;

status = 0;
for (i = 0; i < ARRAY_SIZE(trigger_types); i++)
if ((desc->flags & GPIO_TRIGGER_MASK)
== trigger_types[i].flags) {
status = sprintf(buf, "%s\n",
trigger_types[i].name);
break;
}
}

mutex_unlock(&sysfs_lock);
return status;
}

static ssize_t gpio_edge_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct gpio_desc *desc = dev_get_drvdata(dev);
ssize_t status;
int i;

for (i = 0; i < ARRAY_SIZE(trigger_types); i++)
if (sysfs_streq(trigger_types[i].name, buf))
goto found;
return -EINVAL;

found:
mutex_lock(&sysfs_lock);

if (!test_bit(FLAG_EXPORT, &desc->flags))
status = -EIO;
else {
status = gpio_setup_irq(desc, dev, trigger_types[i].flags);
if (!status)
status = size;
}

mutex_unlock(&sysfs_lock);

return status;
}

static DEVICE_ATTR(edge, 0644, gpio_edge_show, gpio_edge_store);

static const struct attribute *gpio_attrs[] = {
&dev_attr_direction.attr,
&dev_attr_value.attr,
Expand Down Expand Up @@ -473,14 +660,22 @@ int gpio_export(unsigned gpio, bool direction_may_change)
struct device *dev;

dev = device_create(&gpio_class, desc->chip->dev, MKDEV(0, 0),
desc, ioname ? ioname : "gpio%d", gpio);
desc, ioname ? ioname : "gpio%d", gpio);
if (dev) {
if (direction_may_change)
status = sysfs_create_group(&dev->kobj,
&gpio_attr_group);
else
status = device_create_file(dev,
&dev_attr_value);

if (!status && gpio_to_irq(gpio) >= 0
&& (direction_may_change
|| !test_bit(FLAG_IS_OUT,
&desc->flags)))
status = device_create_file(dev,
&dev_attr_edge);

if (status != 0)
device_unregister(dev);
} else
Expand Down Expand Up @@ -572,6 +767,7 @@ void gpio_unexport(unsigned gpio)

dev = class_find_device(&gpio_class, NULL, desc, match_export);
if (dev) {
gpio_setup_irq(desc, dev, 0);
clear_bit(FLAG_EXPORT, &desc->flags);
put_device(dev);
device_unregister(dev);
Expand Down Expand Up @@ -656,6 +852,8 @@ static int __init gpiolib_sysfs_init(void)
unsigned long flags;
unsigned gpio;

idr_init(&pdesc_idr);

status = class_register(&gpio_class);
if (status < 0)
return status;
Expand Down

0 comments on commit 4a0622b

Please sign in to comment.