nuttx/drivers/ioexpander/gpio.c
Justin Hammond 1528b470b9 drivers/ioexpander: List the registered pins in /proc/gpio.
The pins a board publishes are visible in /dev and each can be read
through its own node, but surveying a whole board that way means an open
and two ioctls per pin, and the signal and interrupt counters the upper
half keeps are not reachable through any of them.

Adds a list of registered pins and publishes it as /proc/gpio, behind
GPIO_PROCFS: a quality of life view of the same kind as /proc/pinctrl
and /proc/reset.  Every common field comes from state the upper half
already holds: the pin type, the value through go_read(), how many times
the pin has been registered for signals, and how many interrupts it has
taken.  Lines carry the same key:value tokens in the same order, so the
file is machine parseable.

Lower halves may supply an optional go_describe() adding what only they
can say, such as which pad carries the line or how its trigger is
configured.  It writes text into a caller supplied buffer and the upper
half owns the line, so a lower half needs no procfs knowledge.  A lower
half without it is listed with the common tokens alone.

The pin type index is bounded before use: it comes from the lower half
and the name table cannot cover a type the enumeration does not define.
A pin that cannot be read reports val:- rather than a zero that would
read as a real level.

procfs_register() appends without checking for duplicates, so the entry
is claimed once for the lifetime of the system rather than whenever the
list is empty; pins come and go at run time.

The name is held in a buffer as long as the one gpio_pin_register()
accepts, so a listing always names the same string as /dev.

The pin type name table is declared without an explicit size so that the
assertion beside it compares against the enumeration and can fail; sized
as [GPIO_NPINTYPES] it would have been tautological.

Documents the entry, its tokens, and how a lower half describes a pin.

Off by default: with GPIO_PROCFS unset the list, the lock and the procfs
entry are compiled out, and go_describe() is one more member at the end
of a structure existing lower halves do not reach.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
2026-08-19 00:46:56 +08:00

1301 lines
36 KiB
C

/****************************************************************************
* drivers/ioexpander/gpio.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <nuttx/debug.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <nuttx/fs/fs.h>
#include <nuttx/spinlock.h>
#include <nuttx/ioexpander/gpio.h>
#ifdef CONFIG_GPIO_PROCFS
# include <sys/stat.h>
# include <sys/param.h>
# include <fcntl.h>
# include <nuttx/kmalloc.h>
# include <nuttx/list.h>
# include <nuttx/mutex.h>
# include <nuttx/fs/procfs.h>
#endif
#ifdef CONFIG_DEV_GPIO
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_GPIO_PROCFS
/* One registered pin. struct gpio_dev_s belongs to the lower half, which
* usually embeds it in a larger private structure of its own, so the list
* node lives here rather than being added to it.
*/
/* Long enough for any name gpio_pin_register() accepts: it builds
* "/dev/" plus the name in a 32 byte buffer.
*/
#define GPIO_PROCFS_NAMELEN 28
struct gpio_entry_s
{
struct list_node node;
FAR struct gpio_dev_s *dev;
char name[GPIO_PROCFS_NAMELEN];
};
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
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,
size_t buflen);
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);
#ifdef CONFIG_GPIO_PROCFS
static int gpio_procfs_open(FAR struct file *filep,
FAR const char *relpath,
int oflags, mode_t mode);
static int gpio_procfs_close(FAR struct file *filep);
static ssize_t gpio_procfs_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static int gpio_procfs_dup(FAR const struct file *oldp,
FAR struct file *newp);
static int gpio_procfs_stat(FAR const char *relpath,
FAR struct stat *buf);
static void gpio_procfs_add(FAR struct gpio_dev_s *dev,
FAR const char *pinname);
static void gpio_procfs_remove(FAR struct gpio_dev_s *dev);
#endif
static int gpio_poll(FAR struct file *filep,
FAR struct pollfd *fds, bool setup);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_gpio_drvrops =
{
gpio_open, /* open */
NULL, /* close */
gpio_read, /* read */
gpio_write, /* write */
gpio_seek, /* seek */
gpio_ioctl, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
gpio_poll, /* poll */
};
#ifdef CONFIG_GPIO_PROCFS
static struct list_node g_gpio_list = LIST_INITIAL_VALUE(g_gpio_list);
static mutex_t g_gpio_lock = NXMUTEX_INITIALIZER;
static bool g_gpio_procfs_added;
/* Indexed by enum gpio_pintype_e. The enum's own comment warns that a
* table like this has to be extended with it; the assertion below turns
* forgetting into a build error rather than a pin type with no name.
*/
static const FAR char *g_gpio_typename[] =
{
"INPUT", /* GPIO_INPUT_PIN */
"INPUT_PU", /* GPIO_INPUT_PIN_PULLUP */
"INPUT_PD", /* GPIO_INPUT_PIN_PULLDOWN */
"OUTPUT", /* GPIO_OUTPUT_PIN */
"OUTPUT_OD", /* GPIO_OUTPUT_PIN_OPENDRAIN */
"INT", /* GPIO_INTERRUPT_PIN */
"INT_HIGH", /* GPIO_INTERRUPT_HIGH_PIN */
"INT_LOW", /* GPIO_INTERRUPT_LOW_PIN */
"INT_RISING", /* GPIO_INTERRUPT_RISING_PIN */
"INT_FALLING", /* GPIO_INTERRUPT_FALLING_PIN */
"INT_BOTH", /* GPIO_INTERRUPT_BOTH_PIN */
"INT_WAKE", /* GPIO_INTERRUPT_PIN_WAKEUP */
"INT_HIGH_WAKE", /* GPIO_INTERRUPT_HIGH_PIN_WAKEUP */
"INT_LOW_WAKE", /* GPIO_INTERRUPT_LOW_PIN_WAKEUP */
"INT_RISING_WAKE", /* GPIO_INTERRUPT_RISING_PIN_WAKEUP */
"INT_FALLING_WAKE", /* GPIO_INTERRUPT_FALLING_PIN_WAKEUP */
"INT_BOTH_WAKE", /* GPIO_INTERRUPT_BOTH_PIN_WAKEUP */
};
static_assert(nitems(g_gpio_typename) == GPIO_NPINTYPES,
"pin type name table does not match enum gpio_pintype_e");
static const struct procfs_operations g_gpio_procfs_ops =
{
gpio_procfs_open, /* open */
gpio_procfs_close, /* close */
gpio_procfs_read, /* read */
NULL, /* write */
NULL, /* poll */
gpio_procfs_dup, /* dup */
NULL, /* opendir */
NULL, /* closedir */
NULL, /* readdir */
NULL, /* rewinddir */
gpio_procfs_stat, /* stat */
};
static const struct procfs_entry_s g_gpio_procfs =
{
"gpio", &g_gpio_procfs_ops, PROCFS_FILE_TYPE
};
#endif /* CONFIG_GPIO_PROCFS */
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_GPIO_PROCFS
/****************************************************************************
* Name: gpio_procfs_open
*
* Description:
* Open /proc/gpio. The entry is read only, and holds no state of its
* own beyond the position accounting procfs does for every file.
*
* Input Parameters:
* filep - The file structure to attach the open file to
* relpath - The path below /proc being opened
* oflags - Open flags; anything but read only is refused
* mode - Ignored, the entry cannot be created
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int gpio_procfs_open(FAR struct file *filep, FAR const char *relpath,
int oflags, mode_t mode)
{
FAR struct procfs_file_s *priv;
if ((oflags & O_ACCMODE) != O_RDONLY)
{
return -EACCES;
}
priv = kmm_zalloc(sizeof(struct procfs_file_s));
if (priv == NULL)
{
return -ENOMEM;
}
filep->f_priv = priv;
return OK;
}
/****************************************************************************
* Name: gpio_procfs_close
*
* Description:
* Close /proc/gpio and free what open() allocated.
*
* Input Parameters:
* filep - The open file
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int gpio_procfs_close(FAR struct file *filep)
{
kmm_free(filep->f_priv);
filep->f_priv = NULL;
return OK;
}
/****************************************************************************
* Name: gpio_procfs_read
*
* Description:
* List every registered pin, in registration order, with what the upper
* half knows about it. A lower half that supplies go_describe adds its
* own fields to the same line.
*
* Each read renders from the start and skips what earlier reads already
* returned, so a file longer than the caller's buffer still comes out
* whole across successive reads.
*
* Input Parameters:
* filep - The open file, carrying the offset reached so far
* buffer - Where to return the text
* buflen - Size of buffer
*
* Returned Value:
* The number of bytes returned, zero at end of file, or a negated errno
* on failure.
*
****************************************************************************/
static ssize_t gpio_procfs_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct gpio_entry_s *entry;
size_t remaining = buflen;
FAR char *dest = buffer;
off_t pos = filep->f_pos;
char extra[48];
char line[128];
bool value;
size_t n;
int ret;
ret = nxmutex_lock(&g_gpio_lock);
if (ret < 0)
{
return ret;
}
list_for_every_entry(&g_gpio_list, entry, struct gpio_entry_s, node)
{
FAR const char *type = "-";
if (remaining == 0)
{
break;
}
/* A lower half is free to invent a pin type this table has never
* heard of, so the index is bounded here rather than trusted.
*/
if (entry->dev->gp_pintype < nitems(g_gpio_typename))
{
type = g_gpio_typename[entry->dev->gp_pintype];
}
/* A pin that cannot be read reports -, so that a failed read is
* not shown as a low level.
*/
if (entry->dev->gp_ops->go_read != NULL &&
entry->dev->gp_ops->go_read(entry->dev, &value) >= 0)
{
n = snprintf(line, sizeof(line), "%-12s type:%-18s val:%u",
entry->name, type, value);
}
else
{
n = snprintf(line, sizeof(line), "%-12s type:%-18s val:-",
entry->name, type);
}
n += snprintf(line + n, sizeof(line) - n,
" regs:%u ints:%" PRIuPTR,
entry->dev->register_count, entry->dev->int_count);
extra[0] = '\0';
if (entry->dev->gp_ops->go_describe != NULL)
{
entry->dev->gp_ops->go_describe(entry->dev, extra, sizeof(extra));
extra[sizeof(extra) - 1] = '\0';
}
if (extra[0] != '\0')
{
n += snprintf(line + n, sizeof(line) - n, " %s", extra);
}
n += snprintf(line + n, sizeof(line) - n, "\n");
/* snprintf() reports the length it wanted, so a line longer than the
* buffer would otherwise carry n past it.
*/
if (n >= sizeof(line))
{
n = sizeof(line) - 1;
line[n - 1] = '\n';
}
n = procfs_memcpy(line, n, dest, remaining, &pos);
dest += n;
remaining -= n;
}
nxmutex_unlock(&g_gpio_lock);
filep->f_pos += (dest - buffer);
return dest - buffer;
}
/****************************************************************************
* Name: gpio_procfs_dup
*
* Description:
* Duplicate an open /proc/gpio, copying the position reached so that the
* new file continues where the old one had got to.
*
* Input Parameters:
* oldp - The open file being duplicated
* newp - The file structure to attach the duplicate to
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int gpio_procfs_dup(FAR const struct file *oldp,
FAR struct file *newp)
{
FAR struct procfs_file_s *priv;
priv = kmm_zalloc(sizeof(struct procfs_file_s));
if (priv == NULL)
{
return -ENOMEM;
}
memcpy(priv, oldp->f_priv, sizeof(struct procfs_file_s));
newp->f_priv = priv;
return OK;
}
/****************************************************************************
* Name: gpio_procfs_stat
*
* Description:
* Report /proc/gpio as a read only regular file.
*
* Input Parameters:
* relpath - The path below /proc being queried
* buf - Where to return the status
*
* Returned Value:
* Zero on success, or a negated errno on failure.
*
****************************************************************************/
static int gpio_procfs_stat(FAR const char *relpath, FAR struct stat *buf)
{
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
buf->st_size = 0;
buf->st_blksize = 0;
buf->st_blocks = 0;
return OK;
}
/****************************************************************************
* Name: gpio_procfs_add
*
* Description:
* Remember a pin, and create /proc/gpio when the first one appears.
* procfs_register() has to run before procfs is mounted, which holds
* for the pins a board registers during start up. The entry is claimed
* once for the lifetime of the system, since procfs_register() appends
* without checking for duplicates.
*
* Input Parameters:
* dev - The pin being registered
* pinname - The name it was registered under, used as the /proc/gpio
* label; the caller's string must outlive the pin
*
* Returned Value:
* None. A pin that cannot be listed is still a working pin, so a
* failure here does not fail the registration.
*
****************************************************************************/
static void gpio_procfs_add(FAR struct gpio_dev_s *dev,
FAR const char *pinname)
{
FAR struct gpio_entry_s *entry;
entry = kmm_zalloc(sizeof(struct gpio_entry_s));
if (entry == NULL)
{
return;
}
entry->dev = dev;
strlcpy(entry->name, pinname, sizeof(entry->name));
nxmutex_lock(&g_gpio_lock);
/* procfs_register() appends without checking for a duplicate, so the
* entry is claimed once for the lifetime of the system rather than
* whenever the list is empty. Pins come and go at run time.
*/
if (!g_gpio_procfs_added)
{
procfs_register(&g_gpio_procfs);
g_gpio_procfs_added = true;
}
list_add_tail(&g_gpio_list, &entry->node);
nxmutex_unlock(&g_gpio_lock);
}
/****************************************************************************
* Name: gpio_procfs_remove
*
* Description:
* Forget a pin. /proc/gpio stays, since procfs has no way to withdraw
* an entry, and lists nothing once the last pin has gone.
*
* Input Parameters:
* dev - The pin being unregistered
*
* Returned Value:
* None.
*
****************************************************************************/
static void gpio_procfs_remove(FAR struct gpio_dev_s *dev)
{
FAR struct gpio_entry_s *entry;
nxmutex_lock(&g_gpio_lock);
list_for_every_entry(&g_gpio_list, entry, struct gpio_entry_s, node)
{
if (entry->dev == dev)
{
list_delete(&entry->node);
kmm_free(entry);
break;
}
}
nxmutex_unlock(&g_gpio_lock);
}
#endif /* CONFIG_GPIO_PROCFS */
/****************************************************************************
* Name: gpio_handler
*
* Description:
* GPIO interrupt callback function.
*
****************************************************************************/
static int gpio_handler(FAR struct gpio_dev_s *dev, uint8_t pin)
{
#if CONFIG_DEV_GPIO_NSIGNALS > 0
int i;
#endif
DEBUGASSERT(dev != NULL);
dev->int_count++;
#if CONFIG_DEV_GPIO_NPOLLWAITERS > 0
poll_notify(dev->fds, CONFIG_DEV_GPIO_NPOLLWAITERS, POLLIN);
#endif
#if CONFIG_DEV_GPIO_NSIGNALS > 0
for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++)
{
FAR struct gpio_signal_s *signal = &dev->gp_signals[i];
if (signal->gp_pid == 0)
{
break;
}
nxsig_notification(signal->gp_pid, &signal->gp_event,
SI_QUEUE, &signal->gp_work);
}
#endif
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
*
* Description:
* Standard character driver read method.
*
****************************************************************************/
static ssize_t gpio_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct gpio_dev_s *dev;
int ret;
inode = filep->f_inode;
DEBUGASSERT(inode->i_private != NULL);
dev = inode->i_private;
DEBUGASSERT(buffer != NULL);
if (buflen == 0)
{
return 0; /* Zero will be interpreted as the End-of-File. */
}
/* Check for End-of-File.
*
* REVISIT: Returning End-of-File after one byte has been written permits
* you to cat or echo the GPIO. This, however, is an un-natural use of a
* file position since there is no file position associated with a GPIO.
* It also makes the read() method difficult to use programmatically.
*/
if (filep->f_pos > 0)
{
return 0;
}
/* 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)
{
return ret;
}
/* Convert the GPIO value to ASCII and increment the file position */
buffer[0] += '0';
filep->f_pos = 1;
return 1;
}
/****************************************************************************
* Name: gpio_write
*
* Description:
* Standard character driver write method.
*
* REVISIT: The read() method obeys the semantics of a file position and
* requires re-opening the driver or seeking to address 0. The write()
* method does not. This is an inconsistency.
*
****************************************************************************/
static ssize_t gpio_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct gpio_dev_s *dev;
int ret;
bool val;
inode = filep->f_inode;
DEBUGASSERT(inode->i_private != NULL);
dev = inode->i_private;
DEBUGASSERT(buffer != NULL);
/* Check if this pin is write-able */
if (dev->gp_pintype != GPIO_OUTPUT_PIN &&
dev->gp_pintype != GPIO_OUTPUT_PIN_OPENDRAIN)
{
return -EACCES;
}
/* Verify that a buffer containing data was provided */
DEBUGASSERT(buffer != NULL);
if (buflen != 0)
{
/* Only values '0' and '1' can be written */
if (buffer[0] == '0')
{
val = 0;
}
else if (buffer[0] == '1')
{
val = 1;
}
else
{
return -EINVAL;
}
/* Write the GPIO value */
ret = dev->gp_ops->go_write(dev, val);
if (ret < 0)
{
return ret;
}
/* One byte written */
return 1;
}
return 0;
}
/****************************************************************************
* Name: gpio_seek
*
* Description:
* Reset read flag on seek to 0
*
* REVISIT: Seeking address zero is required to return addition GPIO
* values from read(). This, however, is an un-natural use of a file
* position since there is no file position associated with a GPIO. It
* also makes the read() method difficult to use programmatically.
*
****************************************************************************/
static off_t gpio_seek(FAR struct file *filep, off_t offset, int whence)
{
/* Only SEEK_SET is supported, return ENOSYS for other valid options */
if (whence == SEEK_CUR || whence == SEEK_END)
{
return -ENOSYS;
}
/* Only Offset zero makes sense, POSIX permits setting the file position
* beyond the end of the file, but that makes little sense here.
*/
if (whence == SEEK_SET && offset == 0)
{
filep->f_pos = 0;
return 0;
}
return -EINVAL;
}
/****************************************************************************
* Name: gpio_ioctl
*
* Description:
* Standard character driver ioctl method.
*
****************************************************************************/
static int gpio_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode;
FAR struct gpio_dev_s *dev;
irqstate_t flags;
int ret = OK;
#if CONFIG_DEV_GPIO_NSIGNALS > 0
pid_t pid;
int i;
int j;
#endif
inode = filep->f_inode;
DEBUGASSERT(inode->i_private != NULL);
dev = inode->i_private;
DEBUGASSERT(dev->gp_ops != NULL);
switch (cmd)
{
/* Command: GPIOC_WRITE
* Description: Set the value of an output GPIO
* Argument: 0=output a low value; 1=output a high value
*/
case GPIOC_WRITE:
if (dev->gp_pintype == GPIO_OUTPUT_PIN ||
dev->gp_pintype == GPIO_OUTPUT_PIN_OPENDRAIN)
{
DEBUGASSERT(arg == 0ul || arg == 1ul);
DEBUGASSERT(dev->gp_ops->go_write != NULL);
ret = dev->gp_ops->go_write(dev, (bool)arg);
}
else
{
ret = -EACCES;
}
break;
/* Command: GPIOC_READ
* Description: Read the value of an input or output GPIO
* Argument: A pointer to an bool value to receive the result:
* false=low value; true=high value.
*/
case GPIOC_READ:
{
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);
}
break;
/* Command: GPIOC_PINTYPE
* Description: Return the GPIO pin type.
* Argument: A pointer to an instance of type enum gpio_pintype_e
*/
case GPIOC_PINTYPE:
{
FAR enum gpio_pintype_e *ptr =
(FAR enum gpio_pintype_e *)((uintptr_t)arg);
DEBUGASSERT(ptr != NULL);
*ptr = (enum gpio_pintype_e)dev->gp_pintype;
}
break;
/* Command: GPIOC_REGISTER
* Description: Register to receive a signal whenever there an
* interrupt is received on an input gpio pin. This
* feature, of course, depends upon interrupt GPIO
* support from the platform.
* Argument: The number of signal to be generated when the
* interrupt occurs.
*/
case GPIOC_REGISTER:
if (dev->gp_pintype >= GPIO_INTERRUPT_PIN)
{
flags = enter_critical_section();
#if CONFIG_DEV_GPIO_NSIGNALS > 0
if (arg)
{
pid = nxsched_getpid();
for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++)
{
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;
}
}
#endif
if (dev->register_count++ > 0)
{
leave_critical_section(flags);
break;
}
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)
{
/* Enable pin interrupts */
DEBUGASSERT(dev->gp_ops->go_enable != NULL);
ret = dev->gp_ops->go_enable(dev, true);
}
}
else
{
ret = -EACCES;
}
break;
/* Command: GPIOC_UNREGISTER
* Description: Stop receiving signals for pin interrupts.
* Argument: None.
*/
case GPIOC_UNREGISTER:
if (dev->gp_pintype >= GPIO_INTERRUPT_PIN)
{
flags = enter_critical_section();
#if CONFIG_DEV_GPIO_NSIGNALS > 0
pid = nxsched_getpid();
for (i = 0; i < CONFIG_DEV_GPIO_NSIGNALS; i++)
{
if (pid == dev->gp_signals[i].gp_pid)
{
for (j = i + 1; j < CONFIG_DEV_GPIO_NSIGNALS; j++)
{
if (dev->gp_signals[j].gp_pid == 0)
{
break;
}
}
if (i != --j)
{
memcpy(&dev->gp_signals[i], &dev->gp_signals[j],
sizeof(dev->gp_signals[i]));
}
dev->gp_signals[j].gp_pid = 0;
nxsig_cancel_notification(&dev->gp_signals[j].gp_work);
break;
}
}
#endif
if (--dev->register_count > 0)
{
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
{
ret = -EACCES;
}
break;
/* Command: GPIOC_SETPINTYPE
* Description: Set the GPIO pin type.
* Argument: The enum gpio_pintype_e type.
*/
case GPIOC_SETPINTYPE:
{
enum gpio_pintype_e pintype = (enum gpio_pintype_e)arg;
/* Check if the argument is a valid pintype */
if (pintype >= GPIO_NPINTYPES)
{
ret = -EINVAL;
break;
}
/* Check if the pintype actually needs to be changed */
if (dev->gp_pintype == pintype)
{
/* Pintype remains the same, no need to change anything */
break;
}
/* Disable interrupt if pin had an interrupt pintype previously */
if (dev->gp_pintype >= GPIO_INTERRUPT_PIN)
{
DEBUGASSERT(dev->gp_ops->go_enable != NULL);
ret = dev->gp_ops->go_enable(dev, false);
if (ret < 0)
{
break;
}
}
/* Change pintype */
DEBUGASSERT(dev->gp_ops->go_setpintype != NULL);
ret = dev->gp_ops->go_setpintype(dev, pintype);
if (ret < 0)
{
break;
}
/* Additional DEBUGASSERTs to make sure the right operations are
* available after the pintype has been changed.
*/
DEBUGASSERT(dev->gp_pintype == pintype);
DEBUGASSERT(dev->gp_ops != NULL);
DEBUGASSERT(dev->gp_ops->go_read != NULL);
DEBUGASSERT(dev->gp_ops->go_setpintype != NULL);
if (pintype >= GPIO_OUTPUT_PIN && pintype < GPIO_INTERRUPT_PIN)
{
DEBUGASSERT(dev->gp_ops->go_write != NULL);
}
else if (pintype >= GPIO_INTERRUPT_PIN)
{
DEBUGASSERT(dev->gp_ops->go_attach != NULL);
DEBUGASSERT(dev->gp_ops->go_enable != NULL);
}
}
break;
/* Command: GPIOC_SETDEBOUNCE
* Description: Set the GPIO pin debounce duration.
* Argument: The duration of the channel debounce, uint is ns.
*/
case GPIOC_SETDEBOUNCE:
{
DEBUGASSERT(dev->gp_ops->go_setdebounce != NULL);
ret = dev->gp_ops->go_setdebounce(dev, arg);
break;
}
/* Command: GPIOC_SETMASK
* Description: Mask or unmask the GPIO interrupt without disabling it.
* When masked, the interrupt is suppressed but the
* interrupt source remains enabled.
* Argument: true to mask the interrupt;
* false to unmask the interrupt.
*/
case GPIOC_IRQ_SETMASK:
{
bool mask = (bool)arg;
DEBUGASSERT(dev->gp_ops->go_setmask != NULL);
ret = dev->gp_ops->go_setmask(dev, mask);
break;
}
/* Unrecognized command */
default:
ret = -ENOTTY;
break;
}
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)
{
#if CONFIG_DEV_GPIO_NPOLLWAITERS > 0
FAR struct inode *inode = filep->f_inode;
FAR struct gpio_dev_s *dev = inode->i_private;
int i;
#endif
irqstate_t flags;
int ret = OK;
/* Are we setting up the poll? Or tearing it down? */
flags = enter_critical_section();
if (setup)
{
#if CONFIG_DEV_GPIO_NPOLLWAITERS > 0
/* 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_NPOLLWAITERS; 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_NPOLLWAITERS)
#endif
{
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
****************************************************************************/
/****************************************************************************
* Name: gpio_pin_register
*
* Description:
* Register GPIO pin device driver at /dev/gpioN, where N is the provided
* minor number.
*
* Input Parameters:
* dev - A pointer to a gpio_dev_s
* minor - An integer value to be concatenated with '/dev/gpio'
* to form the device name.
*
* Returned Value:
* Zero on success; A negated errno value is returned on a failure
* all error values returned by inode_reserve:
*
* EINVAL - 'path' is invalid for this operation
* EEXIST - An inode already exists at 'path'
* ENOMEM - Failed to allocate in-memory resources for the operation
*
****************************************************************************/
int gpio_pin_register(FAR struct gpio_dev_s *dev, int minor)
{
char devname[16];
snprintf(devname, sizeof(devname), "gpio%u", (unsigned int)minor);
return gpio_pin_register_byname(dev, devname);
}
/****************************************************************************
* Name: gpio_pin_register_byname
*
* Description:
* Register GPIO pin device driver with it's pin name.
*
* Input Parameters:
* dev - A pointer to a gpio_dev_s
* pinname - A pointer to the name to be concatenated with '/dev/'
* to form the device name.
*
* Returned Value:
* Zero on success; A negated errno value is returned on a failure
* all error values returned by inode_reserve:
*
* EINVAL - 'path' is invalid for this operation
* EEXIST - An inode already exists at 'path'
* ENOMEM - Failed to allocate in-memory resources for the operation
*
****************************************************************************/
int gpio_pin_register_byname(FAR struct gpio_dev_s *dev,
FAR const char *pinname)
{
char devname[32];
int ret;
DEBUGASSERT(dev != NULL && dev->gp_ops != NULL && pinname != NULL);
switch (dev->gp_pintype)
{
case GPIO_INPUT_PIN:
case GPIO_INPUT_PIN_PULLUP:
case GPIO_INPUT_PIN_PULLDOWN:
{
DEBUGASSERT(dev->gp_ops->go_read != NULL);
}
break;
case GPIO_OUTPUT_PIN:
case GPIO_OUTPUT_PIN_OPENDRAIN:
{
DEBUGASSERT(dev->gp_ops->go_read != NULL &&
dev->gp_ops->go_write != NULL);
}
break;
default:
{
DEBUGASSERT(dev->gp_ops->go_read != NULL &&
dev->gp_ops->go_attach != NULL &&
dev->gp_ops->go_enable != NULL);
/* Make sure that the pin interrupt is disabled */
ret = dev->gp_ops->go_enable(dev, false);
if (ret < 0)
{
return ret;
}
}
break;
}
snprintf(devname, sizeof(devname), "/dev/%s", pinname);
gpioinfo("Registering %s\n", devname);
ret = register_driver(devname, &g_gpio_drvrops, 0600, dev);
#ifdef CONFIG_GPIO_PROCFS
if (ret >= 0)
{
gpio_procfs_add(dev, pinname);
}
#endif
return ret;
}
/****************************************************************************
* Name: gpio_pin_unregister
*
* Description:
* Unregister GPIO pin device driver at /dev/gpioN, where N is the provided
* minor number.
*
* Input Parameters:
* dev - A pointer to a gpio_dev_s
* minor - An integer value to be concatenated with '/dev/gpio'
* to form the device name.
*
* Returned Value:
* Zero on success; A negated value is returned on a failure
* (all error values returned by inode_remove):
*
* ENOENT - path does not exist.
* EBUSY - Ref count is not 0;
*
****************************************************************************/
int gpio_pin_unregister(FAR struct gpio_dev_s *dev, int minor)
{
char devname[16];
snprintf(devname, sizeof(devname), "gpio%u", (unsigned int)minor);
return gpio_pin_unregister_byname(dev, devname);
}
/****************************************************************************
* Name: gpio_pin_unregister_byname
*
* Description:
* Unregister GPIO pin device driver at /dev/pinname.
*
* Input Parameters:
* dev - A pointer to a gpio_dev_s
* pinname - A pointer to the name to be concatenated with '/dev/'
* to form the device name.
*
*
* Returned Value:
* Zero on success; A negated value is returned on a failure
* (all error values returned by inode_remove):
*
* ENOENT - path does not exist.
* EBUSY - Ref count is not 0;
****************************************************************************/
int gpio_pin_unregister_byname(FAR struct gpio_dev_s *dev,
FAR const char *pinname)
{
char devname[32];
snprintf(devname, sizeof(devname), "/dev/%s", pinname);
gpioinfo("Unregistering %s\n", devname);
#ifdef CONFIG_GPIO_PROCFS
gpio_procfs_remove(dev);
#endif
return unregister_driver(devname);
}
#endif /* CONFIG_DEV_GPIO */