diff --git a/drivers/ioexpander/gpio.c b/drivers/ioexpander/gpio.c index 5c5fbc767b1..2791df21563 100644 --- a/drivers/ioexpander/gpio.c +++ b/drivers/ioexpander/gpio.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,7 @@ ****************************************************************************/ static int gpio_handler(FAR struct gpio_dev_s *dev, uint8_t pin); +static int gpio_open(FAR struct file *filep); static ssize_t gpio_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t gpio_write(FAR struct file *filep, FAR const char *buffer, @@ -51,6 +53,8 @@ static ssize_t gpio_write(FAR struct file *filep, FAR const char *buffer, static off_t gpio_seek(FAR struct file *filep, off_t offset, int whence); static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +static int gpio_poll(FAR struct file *filep, + FAR struct pollfd *fds, bool setup); /**************************************************************************** * Private Data @@ -58,12 +62,15 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, static const struct file_operations g_gpio_drvrops = { - NULL, /* open */ + gpio_open, /* open */ NULL, /* close */ gpio_read, /* read */ gpio_write, /* write */ gpio_seek, /* seek */ gpio_ioctl, /* ioctl */ + NULL, /* mmap */ + NULL, /* truncate */ + gpio_poll, /* poll */ }; /**************************************************************************** @@ -84,6 +91,10 @@ static int gpio_handler(FAR struct gpio_dev_s *dev, uint8_t pin) DEBUGASSERT(dev != NULL); + dev->int_count++; + + poll_notify(dev->fds, CONFIG_DEV_GPIO_NSIGNALS, POLLIN); + for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++) { FAR struct gpio_signal_s *signal = &dev->gp_signals[i]; @@ -100,6 +111,27 @@ static int gpio_handler(FAR struct gpio_dev_s *dev, uint8_t pin) return OK; } +/**************************************************************************** + * Name: gpio_open + * + * Description: + * Standard character driver open method. + * + ****************************************************************************/ + +static int gpio_open(FAR struct file *filep) +{ + FAR struct inode *inode; + FAR struct gpio_dev_s *dev; + + inode = filep->f_inode; + DEBUGASSERT(inode->i_private != NULL); + dev = inode->i_private; + + filep->f_priv = (FAR void *)dev->int_count; + return OK; +} + /**************************************************************************** * Name: gpio_read * @@ -138,7 +170,9 @@ static ssize_t gpio_read(FAR struct file *filep, FAR char *buffer, return 0; } - /* Read the GPIO value */ + /* Update interrupt count and read the GPIO value */ + + filep->f_priv = (FAR void *)dev->int_count; ret = dev->gp_ops->go_read(dev, (FAR bool *)&buffer[0]); if (ret < 0) @@ -272,9 +306,9 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) FAR struct gpio_dev_s *dev; irqstate_t flags; pid_t pid; - int ret; + int ret = OK; int i; - int j = 0; + int j; inode = filep->f_inode; DEBUGASSERT(inode->i_private != NULL); @@ -313,6 +347,8 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) FAR bool *ptr = (FAR bool *)((uintptr_t)arg); DEBUGASSERT(ptr != NULL); + filep->f_priv = (FAR void *)dev->int_count; + DEBUGASSERT(dev->gp_ops->go_read != NULL); ret = dev->gp_ops->go_read(dev, ptr); DEBUGASSERT(ret < 0 || *ptr == 0 || *ptr == 1); @@ -331,7 +367,6 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) DEBUGASSERT(ptr != NULL); *ptr = (enum gpio_pintype_e)dev->gp_pintype; - ret = OK; } break; @@ -345,44 +380,52 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) */ case GPIOC_REGISTER: - if (arg && dev->gp_pintype >= GPIO_INTERRUPT_PIN) + if (dev->gp_pintype >= GPIO_INTERRUPT_PIN) { - pid = nxsched_getpid(); - flags = spin_lock_irqsave(NULL); - for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++) + flags = enter_critical_section(); + if (arg) { - FAR struct gpio_signal_s *signal = &dev->gp_signals[i]; - - if (signal->gp_pid == 0 || signal->gp_pid == pid) + pid = nxsched_getpid(); + for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++) { - memcpy(&signal->gp_event, (FAR void *)arg, - sizeof(signal->gp_event)); - signal->gp_pid = pid; - ret = OK; + FAR struct gpio_signal_s *signal = &dev->gp_signals[i]; + + if (signal->gp_pid == 0 || signal->gp_pid == pid) + { + memcpy(&signal->gp_event, (FAR void *)arg, + sizeof(signal->gp_event)); + signal->gp_pid = pid; + break; + } + } + + if (i == CONFIG_DEV_GPIO_NSIGNALS) + { + leave_critical_section(flags); + ret = -EBUSY; break; } } - spin_unlock_irqrestore(NULL, flags); - - if (i == 0) + if (dev->register_count++ > 0) { - /* Register our handler */ - - DEBUGASSERT(dev->gp_ops->go_attach != NULL); - ret = dev->gp_ops->go_attach(dev, - (pin_interrupt_t)gpio_handler); - if (ret >= 0) - { - /* Enable pin interrupts */ - - DEBUGASSERT(dev->gp_ops->go_enable != NULL); - ret = dev->gp_ops->go_enable(dev, true); - } + leave_critical_section(flags); + break; } - else if (i == CONFIG_DEV_GPIO_NSIGNALS) + + leave_critical_section(flags); + + /* Register our handler */ + + DEBUGASSERT(dev->gp_ops->go_attach != NULL); + ret = dev->gp_ops->go_attach(dev, + (pin_interrupt_t)gpio_handler); + if (ret >= 0) { - ret = -EBUSY; + /* Enable pin interrupts */ + + DEBUGASSERT(dev->gp_ops->go_enable != NULL); + ret = dev->gp_ops->go_enable(dev, true); } } else @@ -400,7 +443,7 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) if (dev->gp_pintype >= GPIO_INTERRUPT_PIN) { pid = nxsched_getpid(); - flags = spin_lock_irqsave(NULL); + flags = enter_critical_section(); for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++) { if (pid == dev->gp_signals[i].gp_pid) @@ -421,30 +464,28 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) dev->gp_signals[j].gp_pid = 0; nxsig_cancel_notification(&dev->gp_signals[j].gp_work); - ret = OK; break; } - } - - spin_unlock_irqrestore(NULL, flags); - - if (i == 0 && j == 0) - { - /* Make sure that the pin interrupt is disabled */ - - DEBUGASSERT(dev->gp_ops->go_enable != NULL); - ret = dev->gp_ops->go_enable(dev, false); - if (ret >= 0) - { - /* Detach the handler */ - - DEBUGASSERT(dev->gp_ops->go_attach != NULL); - ret = dev->gp_ops->go_attach(dev, NULL); - } } - else if (i == CONFIG_DEV_GPIO_NSIGNALS) + + if (--dev->register_count > 0) { - ret = -EINVAL; + leave_critical_section(flags); + break; + } + + leave_critical_section(flags); + + /* Make sure that the pin interrupt is disabled */ + + DEBUGASSERT(dev->gp_ops->go_enable != NULL); + ret = dev->gp_ops->go_enable(dev, false); + if (ret >= 0) + { + /* Detach the handler */ + + DEBUGASSERT(dev->gp_ops->go_attach != NULL); + ret = dev->gp_ops->go_attach(dev, NULL); } } else @@ -476,7 +517,6 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { /* Pintype remains the same, no need to change anything */ - ret = OK; break; } @@ -532,6 +572,76 @@ static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg) return ret; } +/**************************************************************************** + * Name: gpio_poll + * + * Description: + * Poll method for gpio device. + * + ****************************************************************************/ + +static int gpio_poll(FAR struct file *filep, + FAR struct pollfd *fds, bool setup) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct gpio_dev_s *dev = inode->i_private; + irqstate_t flags; + int ret = OK; + int i; + + /* Are we setting up the poll? Or tearing it down? */ + + flags = enter_critical_section(); + if (setup) + { + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++) + { + /* Find an available slot */ + + if (dev->fds[i] == NULL) + { + /* Bind the poll structure and this slot */ + + dev->fds[i] = fds; + fds->priv = &dev->fds[i]; + + /* Report if a event is pending */ + + if (dev->int_count != (uintptr_t)(filep->f_priv)) + { + poll_notify(&fds, 1, POLLIN); + } + + break; + } + } + + if (i >= CONFIG_DEV_GPIO_NSIGNALS) + { + fds->priv = NULL; + ret = -EBUSY; + } + } + else if (fds->priv != NULL) + { + /* This is a request to tear down the poll. */ + + FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv; + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + + leave_critical_section(flags); + return ret; +} + /**************************************************************************** * Public Functions ****************************************************************************/ diff --git a/include/nuttx/ioexpander/gpio.h b/include/nuttx/ioexpander/gpio.h index f54f68cfd42..fd45ab78220 100644 --- a/include/nuttx/ioexpander/gpio.h +++ b/include/nuttx/ioexpander/gpio.h @@ -152,6 +152,14 @@ struct gpio_dev_s uint8_t gp_pintype; /* See enum gpio_pintype_e */ + /* Number of times the device has been registered by ioctl */ + + uint8_t register_count; + + /* Number of times interrupt occured */ + + uintptr_t int_count; + /* Writable storage used by the upper half driver */ struct gpio_signal_s gp_signals[CONFIG_DEV_GPIO_NSIGNALS]; @@ -162,6 +170,8 @@ struct gpio_dev_s FAR const struct gpio_operations_s *gp_ops; + FAR struct pollfd *fds[CONFIG_DEV_GPIO_NSIGNALS]; + /* Device specific, lower-half information may follow. */ };