diff --git a/drivers/gpio/gpio-mockup.c b/drivers/gpio/gpio-mockup.c index c498b0fbbec8..154d959e8993 100644 --- a/drivers/gpio/gpio-mockup.c +++ b/drivers/gpio/gpio-mockup.c @@ -47,6 +47,7 @@ enum { struct gpio_mockup_line_status { int dir; int value; + int pull; }; struct gpio_mockup_chip { @@ -188,15 +189,56 @@ static int gpio_mockup_to_irq(struct gpio_chip *gc, unsigned int offset) return irq_sim_irqnum(&chip->irqsim, offset); } -static ssize_t gpio_mockup_event_write(struct file *file, - const char __user *usr_buf, - size_t size, loff_t *ppos) +static void gpio_mockup_free(struct gpio_chip *gc, unsigned int offset) +{ + struct gpio_mockup_chip *chip = gpiochip_get_data(gc); + + __gpio_mockup_set(chip, offset, chip->lines[offset].pull); +} + +static ssize_t gpio_mockup_debugfs_read(struct file *file, + char __user *usr_buf, + size_t size, loff_t *ppos) { struct gpio_mockup_dbgfs_private *priv; struct gpio_mockup_chip *chip; struct seq_file *sfile; + struct gpio_chip *gc; + char buf[3]; + int val, rv; + + if (*ppos != 0) + return 0; + + sfile = file->private_data; + priv = sfile->private; + chip = priv->chip; + gc = &chip->gc; + + val = gpio_mockup_get(gc, priv->offset); + snprintf(buf, sizeof(buf), "%d\n", val); + + rv = copy_to_user(usr_buf, buf, sizeof(buf)); + if (rv) + return rv; + + return sizeof(buf) - 1; +} + +static ssize_t gpio_mockup_debugfs_write(struct file *file, + const char __user *usr_buf, + size_t size, loff_t *ppos) +{ + struct gpio_mockup_dbgfs_private *priv; + int rv, val, curr, irq, irq_type; + struct gpio_mockup_chip *chip; + struct seq_file *sfile; struct gpio_desc *desc; - int rv, val; + struct gpio_chip *gc; + struct irq_sim *sim; + + if (*ppos != 0) + return -EINVAL; rv = kstrtoint_from_user(usr_buf, size, 0, &val); if (rv) @@ -206,24 +248,70 @@ static ssize_t gpio_mockup_event_write(struct file *file, sfile = file->private_data; priv = sfile->private; - desc = priv->desc; chip = priv->chip; + gc = &chip->gc; + desc = &gc->gpiodev->descs[priv->offset]; + sim = &chip->irqsim; - gpiod_set_value_cansleep(desc, val); - irq_sim_fire(&chip->irqsim, priv->offset); + mutex_lock(&chip->lock); + + if (test_bit(FLAG_REQUESTED, &desc->flags) && + !test_bit(FLAG_IS_OUT, &desc->flags)) { + curr = __gpio_mockup_get(chip, priv->offset); + if (curr == val) + goto out; + + irq = irq_sim_irqnum(sim, priv->offset); + irq_type = irq_get_trigger_type(irq); + + if ((val == 1 && (irq_type & IRQ_TYPE_EDGE_RISING)) || + (val == 0 && (irq_type & IRQ_TYPE_EDGE_FALLING))) + irq_sim_fire(sim, priv->offset); + } + + /* Change the value unless we're actively driving the line. */ + if (!test_bit(FLAG_REQUESTED, &desc->flags) || + !test_bit(FLAG_IS_OUT, &desc->flags)) + __gpio_mockup_set(chip, priv->offset, val); + +out: + chip->lines[priv->offset].pull = val; + mutex_unlock(&chip->lock); return size; } -static int gpio_mockup_event_open(struct inode *inode, struct file *file) +static int gpio_mockup_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, NULL, inode->i_private); } -static const struct file_operations gpio_mockup_event_ops = { +/* + * Each mockup chip is represented by a directory named after the chip's device + * name under /sys/kernel/debug/gpio-mockup/. Each line is represented by + * a file using the line's offset as the name under the chip's directory. + * + * Reading from the line's file yields the current *value*, writing to the + * line's file changes the current *pull*. Default pull for mockup lines is + * down. + * + * Examples: + * - when a line pulled down is requested in output mode and driven high, its + * value will return to 0 once it's released + * - when the line is requested in output mode and driven high, writing 0 to + * the corresponding debugfs file will change the pull to down but the + * reported value will still be 1 until the line is released + * - line requested in input mode always reports the same value as its pull + * configuration + * - when the line is requested in input mode and monitored for events, writing + * the same value to the debugfs file will be a noop, while writing the + * opposite value will generate a dummy interrupt with an appropriate edge + */ +static const struct file_operations gpio_mockup_debugfs_ops = { .owner = THIS_MODULE, - .open = gpio_mockup_event_open, - .write = gpio_mockup_event_write, + .open = gpio_mockup_debugfs_open, + .read = gpio_mockup_debugfs_read, + .write = gpio_mockup_debugfs_write, .llseek = no_llseek, }; @@ -258,7 +346,7 @@ static void gpio_mockup_debugfs_setup(struct device *dev, priv->desc = &gc->gpiodev->descs[i]; evfile = debugfs_create_file(name, 0200, chip->dbg_dir, priv, - &gpio_mockup_event_ops); + &gpio_mockup_debugfs_ops); if (IS_ERR_OR_NULL(evfile)) goto err; } @@ -266,7 +354,7 @@ static void gpio_mockup_debugfs_setup(struct device *dev, return; err: - dev_err(dev, "error creating debugfs event files\n"); + dev_err(dev, "error creating debugfs files\n"); } static int gpio_mockup_name_lines(struct device *dev, @@ -342,6 +430,7 @@ static int gpio_mockup_probe(struct platform_device *pdev) gc->direction_input = gpio_mockup_dirin; gc->get_direction = gpio_mockup_get_direction; gc->to_irq = gpio_mockup_to_irq; + gc->free = gpio_mockup_free; chip->lines = devm_kcalloc(dev, gc->ngpio, sizeof(*chip->lines), GFP_KERNEL); @@ -415,7 +504,7 @@ static int __init gpio_mockup_init(void) return -EINVAL; } - gpio_mockup_dbg_dir = debugfs_create_dir("gpio-mockup-event", NULL); + gpio_mockup_dbg_dir = debugfs_create_dir("gpio-mockup", NULL); if (IS_ERR_OR_NULL(gpio_mockup_dbg_dir)) gpio_mockup_err("error creating debugfs directory\n");