|
@@ -1,5 +1,6 @@
|
|
#include <linux/kernel.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
|
|
+#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/device.h>
|
|
#include <linux/device.h>
|
|
@@ -7,6 +8,7 @@
|
|
#include <linux/debugfs.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/gpio.h>
|
|
|
|
+#include <linux/idr.h>
|
|
|
|
|
|
|
|
|
|
/* Optional implementation infrastructure for GPIO interfaces.
|
|
/* Optional implementation infrastructure for GPIO interfaces.
|
|
@@ -49,6 +51,13 @@ struct gpio_desc {
|
|
#define FLAG_RESERVED 2
|
|
#define FLAG_RESERVED 2
|
|
#define FLAG_EXPORT 3 /* protected by sysfs_lock */
|
|
#define FLAG_EXPORT 3 /* protected by sysfs_lock */
|
|
#define FLAG_SYSFS 4 /* exported via /sys/class/gpio/control */
|
|
#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
|
|
#ifdef CONFIG_DEBUG_FS
|
|
const char *label;
|
|
const char *label;
|
|
@@ -56,6 +65,15 @@ struct gpio_desc {
|
|
};
|
|
};
|
|
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
|
|
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)
|
|
static inline void desc_set_label(struct gpio_desc *d, const char *label)
|
|
{
|
|
{
|
|
#ifdef CONFIG_DEBUG_FS
|
|
#ifdef CONFIG_DEBUG_FS
|
|
@@ -188,10 +206,10 @@ static DEFINE_MUTEX(sysfs_lock);
|
|
* /value
|
|
* /value
|
|
* * always readable, subject to hardware behavior
|
|
* * always readable, subject to hardware behavior
|
|
* * may be writable, as zero/nonzero
|
|
* * 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,
|
|
static ssize_t gpio_direction_show(struct device *dev,
|
|
@@ -288,6 +306,175 @@ static ssize_t gpio_value_store(struct device *dev,
|
|
static /*const*/ DEVICE_ATTR(value, 0644,
|
|
static /*const*/ DEVICE_ATTR(value, 0644,
|
|
gpio_value_show, gpio_value_store);
|
|
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[] = {
|
|
static const struct attribute *gpio_attrs[] = {
|
|
&dev_attr_direction.attr,
|
|
&dev_attr_direction.attr,
|
|
&dev_attr_value.attr,
|
|
&dev_attr_value.attr,
|
|
@@ -473,7 +660,7 @@ int gpio_export(unsigned gpio, bool direction_may_change)
|
|
struct device *dev;
|
|
struct device *dev;
|
|
|
|
|
|
dev = device_create(&gpio_class, desc->chip->dev, MKDEV(0, 0),
|
|
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 (dev) {
|
|
if (direction_may_change)
|
|
if (direction_may_change)
|
|
status = sysfs_create_group(&dev->kobj,
|
|
status = sysfs_create_group(&dev->kobj,
|
|
@@ -481,6 +668,14 @@ int gpio_export(unsigned gpio, bool direction_may_change)
|
|
else
|
|
else
|
|
status = device_create_file(dev,
|
|
status = device_create_file(dev,
|
|
&dev_attr_value);
|
|
&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)
|
|
if (status != 0)
|
|
device_unregister(dev);
|
|
device_unregister(dev);
|
|
} else
|
|
} else
|
|
@@ -572,6 +767,7 @@ void gpio_unexport(unsigned gpio)
|
|
|
|
|
|
dev = class_find_device(&gpio_class, NULL, desc, match_export);
|
|
dev = class_find_device(&gpio_class, NULL, desc, match_export);
|
|
if (dev) {
|
|
if (dev) {
|
|
|
|
+ gpio_setup_irq(desc, dev, 0);
|
|
clear_bit(FLAG_EXPORT, &desc->flags);
|
|
clear_bit(FLAG_EXPORT, &desc->flags);
|
|
put_device(dev);
|
|
put_device(dev);
|
|
device_unregister(dev);
|
|
device_unregister(dev);
|
|
@@ -656,6 +852,8 @@ static int __init gpiolib_sysfs_init(void)
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
unsigned gpio;
|
|
unsigned gpio;
|
|
|
|
|
|
|
|
+ idr_init(&pdesc_idr);
|
|
|
|
+
|
|
status = class_register(&gpio_class);
|
|
status = class_register(&gpio_class);
|
|
if (status < 0)
|
|
if (status < 0)
|
|
return status;
|
|
return status;
|