This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 36f91643ae2e85492c0b5a9ad46e5fdb01f3db47 Author: yezhonghui <[email protected]> AuthorDate: Wed Apr 2 09:49:34 2025 +0800 sim/gpiochip: add sim gpiochip for gpio autotest add sim gpiochip driver Signed-off-by: yezhonghui <[email protected]> --- Documentation/platforms/sim/index.rst | 1 + Documentation/platforms/sim/sim_gpiochip.rst | 308 ++++++++++++++++++ arch/sim/Kconfig | 32 ++ arch/sim/src/Makefile | 5 + arch/sim/src/sim/CMakeLists.txt | 5 + arch/sim/src/sim/posix/sim_linux_gpiochip.c | 419 ++++++++++++++++++++++++ arch/sim/src/sim/sim_gpiochip.c | 454 +++++++++++++++++++++++++++ arch/sim/src/sim/sim_gpiochip.h | 71 +++++ arch/sim/src/sim/sim_internal.h | 4 + 9 files changed, 1299 insertions(+) diff --git a/Documentation/platforms/sim/index.rst b/Documentation/platforms/sim/index.rst index ddd06b6606a..84adbf17c8a 100644 --- a/Documentation/platforms/sim/index.rst +++ b/Documentation/platforms/sim/index.rst @@ -14,3 +14,4 @@ The following Simulator/Emulators are supported: */* network_linux network_vpnkit + sim_gpiochip diff --git a/Documentation/platforms/sim/sim_gpiochip.rst b/Documentation/platforms/sim/sim_gpiochip.rst new file mode 100644 index 00000000000..57097ffb95d --- /dev/null +++ b/Documentation/platforms/sim/sim_gpiochip.rst @@ -0,0 +1,308 @@ +====================================== +Sim GPIO Chip Driver (Linux Host GPIO) +====================================== + +Overview +======== + +The Sim GPIO Chip driver provides a mechanism for NuttX simulation (sim) to access +the Linux host's GPIO chip devices (``/dev/gpiochipN``). This allows NuttX applications +running in simulation mode to interact with real hardware GPIO pins connected to the +Linux host system. + +This driver is particularly useful for: + +- Testing GPIO-based applications in a simulated environment with real hardware +- Interfacing with USB-to-GPIO adapters (e.g., CH341A) from NuttX simulation +- Developing and debugging GPIO drivers without dedicated embedded hardware + +Host Prepare +============ + +Preparation required on the host side: + +- Hardware module required: USB-CH341A module +- Refer to https://github.com/frank-zago/ch341-i2c-spi-gpio, and install the driver +- Verify existence of /dev/gpiochipN device file + +Architecture +============ + +The driver consists of two layers: + +1. **NuttX Layer** (``sim_gpiochip.c``): Implements the NuttX ``ioexpander_dev_s`` + interface, providing standard GPIO operations to upper-layer NuttX drivers. + +2. **Host Layer** (``sim_linux_gpiochip.c``): Interfaces directly with Linux kernel's + GPIO character device (``/dev/gpiochipN``) using the GPIO v2 ABI. + +:: + + +---------------------+ + | NuttX Application | + +---------------------+ + | + v + +---------------------+ + | GPIO Lower Half | + | (gpio_lower_half) | + +---------------------+ + | + v + +---------------------+ + | sim_gpiochip.c | <-- NuttX ioexpander interface + | (ioexpander_dev_s) | + +---------------------+ + | + v + +---------------------+ + | sim_linux_gpiochip.c| <-- Linux host GPIO interface + | (GPIO v2 ABI) | + +---------------------+ + | + v + +---------------------+ + | /dev/gpiochipN | <-- Linux GPIO character device + +---------------------+ + +Header Files +============ + +- ``arch/sim/src/sim/sim_hostgpiochip.h``: Host GPIO chip interface definitions + and function prototypes. + +- ``include/nuttx/ioexpander/ioexpander.h``: Standard NuttX IO expander interface. + +- ``include/nuttx/ioexpander/gpio.h``: NuttX GPIO interface definitions. + +Configuration Options +===================== + +The following configuration options are relevant to this driver: + +- ``CONFIG_SIM_GPIOCHIP``: Enable the sim GPIO chip driver. +- ``CONFIG_IOEXPANDER_NPINS``: Maximum number of GPIO pins supported (default: 64). +- ``CONFIG_IOEXPANDER_INT_ENABLE``: Enable interrupt support for GPIO pins. + +Supported Operations +==================== + +The driver supports the following GPIO operations: + +Direction Control +----------------- + +.. code-block:: c + + int sim_gpiochip_direction(struct ioexpander_dev_s *dev, + uint8_t pin, int direction); + +Set GPIO pin direction. Supported directions: + +- ``IOEXPANDER_DIRECTION_IN``: Configure as input +- ``IOEXPANDER_DIRECTION_OUT``: Configure as output +- ``IOEXPANDER_DIRECTION_OUT_OPENDRAIN``: Configure as open-drain output + +Read/Write Pin +-------------- + +.. code-block:: c + + int sim_gpiochip_readpin(struct ioexpander_dev_s *dev, uint8_t pin, + bool *value); + int sim_gpiochip_writepin(struct ioexpander_dev_s *dev, uint8_t pin, + bool value); + +Read or write the value of a GPIO pin. + +Interrupt Configuration +----------------------- + +.. code-block:: c + + int sim_gpiochip_option(struct ioexpander_dev_s *dev, uint8_t pin, + int option, void *val); + +Configure GPIO pin options. Supported interrupt edge configurations: + +- ``IOEXPANDER_VAL_RISING``: Trigger on rising edge +- ``IOEXPANDER_VAL_FALLING``: Trigger on falling edge +- ``IOEXPANDER_VAL_BOTH``: Trigger on both edges +- ``IOEXPANDER_VAL_DISABLE``: Disable interrupt + +Interrupt Callback +------------------ + +.. code-block:: c + + void *sim_gpiochip_attach(struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, + ioe_callback_t callback, + void *arg); + int sim_gpiochip_detach(struct ioexpander_dev_s *dev, void *handle); + +Attach or detach an interrupt callback function for GPIO pins. + +Host Layer API +============== + +The host layer (``sim_linux_gpiochip.c``) provides the following functions: + +.. code-block:: c + + /* Allocate and initialize a host GPIO chip device */ + struct host_gpiochip_dev *host_gpiochip_alloc(const char *filename); + + /* Free a host GPIO chip device */ + void host_gpiochip_free(struct host_gpiochip_dev *dev); + + /* Set GPIO pin direction */ + int host_gpiochip_direction(struct host_gpiochip_dev *dev, + uint8_t pin, bool input); + + /* Read GPIO pin value */ + int host_gpiochip_readpin(struct host_gpiochip_dev *dev, + uint8_t pin, bool *value); + + /* Write GPIO pin value */ + int host_gpiochip_writepin(struct host_gpiochip_dev *dev, + uint8_t pin, bool value); + + /* Request GPIO interrupt */ + int host_gpiochip_irq_request(struct host_gpiochip_dev *dev, + uint8_t pin, uint16_t cfgset); + + /* Check if GPIO interrupt is active */ + bool host_gpiochip_irq_active(struct host_gpiochip_dev *dev, uint8_t pin); + + /* Get GPIO line information */ + int host_gpiochip_get_line(struct host_gpiochip_dev *priv, + uint8_t pin, bool *input); + +Linux Kernel Version Requirements +================================= + +The driver uses Linux GPIO v2 ABI, which requires: + +- **Linux kernel >= 6.8.0**: Full functionality with GPIO v2 API support. +- **Linux kernel < 6.8.0**: The driver compiles but provides stub implementations + that return 0 or NULL. + +Usage Example +============= + +Initialization +-------------- + +.. code-block:: c + + #include <nuttx/ioexpander/gpio.h> + #include "sim_internal.h" + + int board_gpio_initialize(void) + { + struct ioexpander_dev_s *ioe; + int ret; + + /* Initialize the GPIO chip device */ + ioe = sim_gpiochip_initialize("/dev/gpiochip0"); + if (ioe == NULL) + { + return -ENODEV; + } + + /* Register GPIO pins using gpio_lower_half */ + ret = gpio_lower_half(ioe, 0, GPIO_INPUT_PIN, 60); /* Pin 0 as input, minor 60 */ + if (ret < 0) + { + return ret; + } + + ret = gpio_lower_half(ioe, 1, GPIO_OUTPUT_PIN, 61); /* Pin 1 as output, minor 61 */ + if (ret < 0) + { + return ret; + } + + return OK; + } + +Application Usage +----------------- + +After initialization, GPIO pins can be accessed through standard NuttX GPIO interface: + +.. code-block:: c + + #include <fcntl.h> + #include <sys/ioctl.h> + #include <nuttx/ioexpander/gpio.h> + + int main(void) + { + int fd; + bool value; + + /* Open GPIO device */ + fd = open("/dev/gpio60", O_RDWR); + if (fd < 0) + { + return -1; + } + + /* Read GPIO value */ + ioctl(fd, GPIOC_READ, &value); + printf("GPIO value: %d\n", value); + + close(fd); + return 0; + } + +Interrupt Handling +================== + +The driver uses a work queue to poll for GPIO events. The polling interval is +defined by ``SIM_GPIOCHIP_WORK_DELAY`` (default: 500 microseconds). + +When an interrupt event is detected on a GPIO pin, the registered callback +function is invoked with the pin number and user-provided argument. + +.. code-block:: c + + static int gpio_interrupt_handler(struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, void *arg) + { + printf("GPIO interrupt on pin %d\n", pinset); + return OK; + } + + /* Attach interrupt handler */ + void *handle = IOEP_ATTACH(ioe, (1 << pin), gpio_interrupt_handler, NULL); + + /* Configure interrupt edge */ + IOEP_SETOPTION(ioe, pin, IOEXPANDER_OPTION_INTCFG, + (void *)IOEXPANDER_VAL_RISING); + +Files +===== + +- ``arch/sim/src/sim/sim_gpiochip.c``: NuttX IO expander implementation +- ``arch/sim/src/sim/posix/sim_linux_gpiochip.c``: Linux host GPIO interface +- ``arch/sim/src/sim/sim_hostgpiochip.h``: Host GPIO chip header file + +Limitations +=========== + +1. **Polling-based interrupts**: Due to simulation constraints, interrupts are + implemented using polling rather than true hardware interrupts. + +2. **Linux kernel version**: Full functionality requires Linux kernel >= 6.8.0. + +3. **Pin count**: Limited by ``CONFIG_IOEXPANDER_NPINS`` configuration. + +4. **Invert option**: The ``IOEXPANDER_OPTION_INVERT`` option is not yet implemented. + +See Also +======== + +- Linux GPIO documentation: https://www.kernel.org/doc/html/latest/driver-api/gpio/ diff --git a/arch/sim/Kconfig b/arch/sim/Kconfig index 08c12e66492..944a12c6c7e 100644 --- a/arch/sim/Kconfig +++ b/arch/sim/Kconfig @@ -661,6 +661,38 @@ config SIM_SPIDEV_NAME endif +config SIM_GPIOCHIP + bool "Simulated gpiochip" + default n + depends on IOEXPANDER + ---help--- + Build in support for simulated gpiochip + +if SIM_GPIOCHIP + +choice + prompt "Simulated GPIOCHIP Type" + default SIM_GPIOCHIP_LINUX + +config SIM_GPIOCHIP_LINUX + bool "Linux Gpiochip Character Dev" + depends on HOST_LINUX + ---help--- + Attach a Linux Gpiochip via the character device + interface. To achieve a SPI port on Linux host, it is + recommended to use a USB<>GPIO device such as CH341A/B. + +endchoice + +config SIM_GPIOCHIP_NAME + string "the name of host gpiochip dev to attach to simulator" + default "/dev/gpiochip0" + ---help--- + This is the name of the gpiochip device on the host implementation to + attach to the simulator driver. + +endif + menu "Simulated UART" config SIM_UART_DMA diff --git a/arch/sim/src/Makefile b/arch/sim/src/Makefile index d0ef55dae4b..542a0567470 100644 --- a/arch/sim/src/Makefile +++ b/arch/sim/src/Makefile @@ -246,6 +246,11 @@ ifeq ($(CONFIG_SIM_SPI_LINUX),y) HOSTSRCS += sim_linuxspi.c endif +ifeq ($(CONFIG_SIM_GPIOCHIP_LINUX),y) + HOSTSRCS += sim_linux_gpiochip.c + CSRCS += sim_gpiochip.c +endif + ifeq ($(CONFIG_SIM_USB_DEV),y) CSRCS += sim_usbdev.c ifeq ($(CONFIG_SIM_USB_RAW_GADGET),y) diff --git a/arch/sim/src/sim/CMakeLists.txt b/arch/sim/src/sim/CMakeLists.txt index 66dc3db8944..3b3da83e189 100644 --- a/arch/sim/src/sim/CMakeLists.txt +++ b/arch/sim/src/sim/CMakeLists.txt @@ -249,6 +249,11 @@ if(CONFIG_SIM_SPI_LINUX) list(APPEND HOSTSRCS sim_linuxspi.c) endif() +if(CONFIG_SIM_GPIOCHIP_LINUX) + list(APPEND SRCS sim_gpiochip.c) + list(APPEND HOSTSRCS sim_linux_gpiochip.c) +endif() + if(CONFIG_SIM_USB_DEV) list(APPEND SRCS sim_usbdev.c) if(CONFIG_SIM_USB_RAW_GADGET) diff --git a/arch/sim/src/sim/posix/sim_linux_gpiochip.c b/arch/sim/src/sim/posix/sim_linux_gpiochip.c new file mode 100644 index 00000000000..e6bdd252647 --- /dev/null +++ b/arch/sim/src/sim/posix/sim_linux_gpiochip.c @@ -0,0 +1,419 @@ +/**************************************************************************** + * arch/sim/src/sim/posix/sim_linux_gpiochip.c + * + * 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 <sys/types.h> +#include <sys/ioctl.h> + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/const.h> +#include <linux/ioctl.h> +#include <linux/types.h> +#include <linux/gpio.h> + +#include "sim_gpiochip.h" +#include "sim_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define gpioerr(fmt, ...) \ + syslog(LOG_ERR, "sim_linux_gpiochip: " fmt "\n", ##__VA_ARGS__) +#define gpioinfo(fmt, ...) \ + syslog(LOG_ERR, "sim_linux_gpiochip: " fmt "\n", ##__VA_ARGS__) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: host_gpiochip_direction + * + * Description: + * Provide gpiochip pin direction config. + * + * Input Parameters: + * priv - A pointer to instance of Linux gpiochip. + * pin - The pin number. + * input - The direction of the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +int host_gpiochip_direction(struct host_gpiochip_dev *priv, uint8_t pin, + bool input) +{ + struct gpio_v2_line_request req; + int nonblock = 1; + int ret; + + memset(&req, 0, sizeof(req)); + req.num_lines = 1; + req.offsets[0] = pin; + + if (priv->line_fd[pin] > 0) + { + close(priv->line_fd[pin]); + priv->line_fd[pin] = -1; + } + + if (input) + { + req.config.flags = GPIO_V2_LINE_FLAG_INPUT; + } + else + { + req.config.flags = GPIO_V2_LINE_FLAG_OUTPUT; + } + + snprintf(req.consumer, sizeof(req.consumer) - 1, "gpio%d", pin); + ret = host_uninterruptible(ioctl, priv->file, GPIO_V2_GET_LINE_IOCTL, + &req); + if (ret < 0) + { + gpioerr("ERROR: pin %d set direction failed\n", pin); + return -errno; + } + + ret = host_uninterruptible(ioctl, req.fd, FIONBIO, &nonblock); + if (ret < 0) + { + gpioerr("ERROR: Failed to set non-blocking: %s\n", strerror(errno)); + return -errno; + } + + priv->line_fd[pin] = req.fd; + + return 0; +} + +/**************************************************************************** + * Name: host_gpiochip_irq_request + * + * Input Parameters: + * priv - A pointer to instance of Linux gpiochip. + * pin - The pin number. + * cfgset - The config set of the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +int host_gpiochip_irq_request(struct host_gpiochip_dev *priv, uint8_t pin, + uint16_t cfg) +{ + struct gpio_v2_line_request req; + int nonblock = 1; + int ret; + + if (priv->line_fd[pin] > 0) + { + close(priv->line_fd[pin]); + priv->line_fd[pin] = -1; + } + + memset(&req, 0, sizeof(req)); + switch (cfg) + { + case GPIOCHIP_LINE_FLAG_FALLING: + req.config.flags = GPIO_V2_LINE_FLAG_EDGE_FALLING; + break; + case GPIOCHIP_LINE_FLAG_RISING: + req.config.flags = GPIO_V2_LINE_FLAG_EDGE_RISING; + break; + case GPIOCHIP_LINE_FLAG_BOTH: + req.config.flags = GPIO_V2_LINE_FLAG_EDGE_FALLING | + GPIO_V2_LINE_FLAG_EDGE_RISING; + break; + default: + req.config.flags = 0; + break; + } + + req.offsets[0] = pin; + req.num_lines = 1; + req.config.flags |= GPIO_V2_LINE_FLAG_INPUT; + if (req.config.flags != GPIO_V2_LINE_FLAG_INPUT) + { + snprintf(req.consumer, sizeof(req.consumer) - 1, "gpio-irq%d", pin); + } + + /* Warn only pin 10 can register in ch341A */ + + ret = host_uninterruptible(ioctl, priv->file, GPIO_V2_GET_LINE_IOCTL, + &req); + if (ret < 0) + { + gpioerr("ERROR: ioctl failed: %s \n", strerror(errno)); + return -errno; + } + + ret = host_uninterruptible(ioctl, req.fd, FIONBIO, &nonblock); + if (ret < 0) + { + gpioerr("ERROR: Failed to set non-blocking: %s\n", strerror(errno)); + return -errno; + } + + priv->line_fd[pin] = req.fd; + + return 0; +} + +/**************************************************************************** + * Name: host_gpiochip_writepin + * + * Description: + * Write gpiochip pin value. + * + * Input Parameters: + * priv - A pointer to instance of Linux gpiochip. + * pin - The pin number. + * value - The value write to the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +int host_gpiochip_writepin(struct host_gpiochip_dev *priv, uint8_t pin, + bool value) +{ + struct gpio_v2_line_values vals; + int ret; + + if (priv->line_fd[pin] <= 0) + { + gpioerr("ERROR: Invalid pin %d config\n", pin); + return -EINVAL; + } + + memset(&vals, 0, sizeof(vals)); + vals.mask = 1; + vals.bits = !!value; + + ret = host_uninterruptible(ioctl, priv->line_fd[pin], + GPIO_V2_LINE_SET_VALUES_IOCTL, &vals); + if (ret < 0) + { + gpioerr("ERROR: Failed to set pin %d value %d\n", pin, value); + return -errno; + } + + return 0; +} + +/**************************************************************************** + * Name: host_gpiochip_readpin + * + * Description: + * Read gpiochip pin value. + * + * Input Parameters: + * priv - A pointer to instance of Linux gpiochip. + * pin - The pin number. + * value - The value write to the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +int host_gpiochip_readpin(struct host_gpiochip_dev *priv, uint8_t pin, + bool *value) +{ + struct gpio_v2_line_values vals; + int ret; + + if (priv->line_fd[pin] <= 0) + { + gpioerr("ERROR: Invalid pin %d config\n", pin); + return -EINVAL; + } + + memset(&vals, 0, sizeof(vals)); + vals.mask = 1; + ret = host_uninterruptible(ioctl, priv->line_fd[pin], + GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); + if (ret < 0) + { + gpioerr("ERROR: Failed to get pin%d value, errno[%d]\n", pin, errno); + return -errno; + } + + *value = !!(vals.bits & 0x01); + + return 0; +} + +/**************************************************************************** + * Name: host_gpiochip_irq_active + * + * Description: + * register gpio for gpiochip device + * + * Input Parameters: + * priv - A pointer to instance of Linux gpiochip. + * pin - gpio pin of Linux gpiochip device. + * + * Returned Value: + * 0 for OK. + * + ****************************************************************************/ + +bool host_gpiochip_irq_active(struct host_gpiochip_dev *priv, uint8_t pin) +{ + if (priv->line_fd[pin] > 0) + { + struct gpio_v2_line_event ev; + int fd = priv->line_fd[pin]; + memset(&ev, 0, sizeof(ev)); + if (host_uninterruptible(read, fd, &ev, sizeof(ev)) == sizeof(ev)) + { + return true; + } + } + + return false; +} + +/**************************************************************************** + * Name: host_gpiochip_get_line + * + * Description: + * Get line info from gpiochip device + * + * Input Parameters: + * priv - A pointer to instance of Linux gpiochip. + * pin - gpio line of Linux gpiochip. + * input - A pointer to direction of gpioline. + * + * Returned Value: + * 0 for OK. + * + ****************************************************************************/ + +int host_gpiochip_get_line(struct host_gpiochip_dev *priv, uint8_t pin, + bool *input) +{ + struct gpio_v2_line_info info; + int ret; + + memset(&info, 0, sizeof(info)); + info.offset = pin; + ret = host_uninterruptible(ioctl, priv->file, GPIO_V2_GET_LINEINFO_IOCTL, + &info); + if (ret < 0) + { + gpioerr("Failed to get line info: %d", ret); + return -errno; + } + + if (info.flags & GPIO_V2_LINE_FLAG_USED) + { + return 1; + } + + if (info.flags & GPIO_V2_LINE_FLAG_OUTPUT) + { + *input = false; + } + else + { + *input = true; + } + + return 0; +} + +/**************************************************************************** + * Name: host_gpiochip_alloc + * + * Description: + * Initialize one gpiochip device + * + * Input Parameters: + * filename - the name of gpiochip device in Linux, e.g. "/dev/gpiochipN". + * + * Returned Value: + * The pointer to the instance of Linux gpiochip device. + * + ****************************************************************************/ + +struct host_gpiochip_dev *host_gpiochip_alloc(const char *filename) +{ + struct host_gpiochip_dev *dev; + + dev = malloc(sizeof(struct host_gpiochip_dev)); + if (!dev) + { + gpioerr("Failed to allocate memory for gpiochip device"); + return NULL; + } + + dev->file = host_uninterruptible(open, filename, O_RDWR | O_CLOEXEC); + if (dev->file < 0) + { + gpioerr("Failed to open %s: %d", filename, dev->file); + free(dev); + return NULL; + } + + return dev; +} + +/**************************************************************************** + * Name: host_gpiochip_free + * + * Description: + * Uninitialize an gpiochip device + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void host_gpiochip_free(struct host_gpiochip_dev *priv) +{ + host_uninterruptible(close, priv->file); + free(priv); +} diff --git a/arch/sim/src/sim/sim_gpiochip.c b/arch/sim/src/sim/sim_gpiochip.c new file mode 100644 index 00000000000..8ce4bba6df1 --- /dev/null +++ b/arch/sim/src/sim/sim_gpiochip.c @@ -0,0 +1,454 @@ +/**************************************************************************** + * arch/sim/src/sim/sim_gpiochip.c + * + * 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 <debug.h> +#include <nuttx/wdog.h> +#include <nuttx/ioexpander/gpio.h> +#include <nuttx/ioexpander/ioexpander.h> + +#include "sim_gpiochip.h" +#include "sim_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define GPIOCHIP_LINE_BASE 60 +#define SIM_GPIOCHIP_WDOG_DELAY USEC2TICK(500) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct sim_gpiochip_callback_s +{ + ioe_callback_t cbfunc; + void *cbarg; +}; + +struct sim_gpiochip_dev_s +{ + const struct ioexpander_ops_s *ops; /* gpiochip vtable */ + struct sim_gpiochip_callback_s cb[CONFIG_IOEXPANDER_NPINS]; + struct host_gpiochip_dev *dev; + struct wdog_s wdog; /* poll work */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int sim_gpiochip_direction(struct ioexpander_dev_s *dev, + uint8_t pin, int direction); +static int sim_gpiochip_option(struct ioexpander_dev_s *dev, uint8_t pin, + int option, void *val); +static int sim_gpiochip_writepin(struct ioexpander_dev_s *dev, uint8_t pin, + bool value); +static int sim_gpiochip_readpin(struct ioexpander_dev_s *dev, uint8_t pin, + bool *value); +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +static void *sim_gpiochip_attach(struct ioexpander_dev_s *dev, + uint16_t pinset, + ioe_callback_t callback, + void *arg); +static int sim_gpiochip_detach(struct ioexpander_dev_s *dev, + void *handle); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct ioexpander_ops_s g_sim_gpiochip_ops = +{ + .ioe_direction = sim_gpiochip_direction, + .ioe_option = sim_gpiochip_option, + .ioe_writepin = sim_gpiochip_writepin, + .ioe_readpin = sim_gpiochip_readpin, + .ioe_readbuf = sim_gpiochip_readpin, +#ifdef CONFIG_IOEXPANDER_INT_ENABLE + .ioe_attach = sim_gpiochip_attach, + .ioe_detach = sim_gpiochip_detach, +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: sim_gpiochip_direction + * + * Description: + * Provide gpiochip pin direction config. + * + * Input Parameters: + * dev - A pointer to instance of sim gpiochip device. + * pin - The pin number. + * direction - The direction of the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +static int sim_gpiochip_direction(struct ioexpander_dev_s *dev, + uint8_t pin, int direction) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)dev; + + if (direction == IOEXPANDER_DIRECTION_OUT || + direction == IOEXPANDER_DIRECTION_OUT_OPENDRAIN) + { + return host_gpiochip_direction(priv->dev, pin, false); + } + else + { + return host_gpiochip_direction(priv->dev, pin, true); + } +} + +/**************************************************************************** + * Name: sim_gpiochip_option + * + * Description: + * Provide gpiochip pin option. + * + * Input Parameters: + * dev - A pointer to instance of sim gpiochip device. + * pin - The pin number. + * option.- The option type. + * val - A pointer to val of the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +static int sim_gpiochip_option(struct ioexpander_dev_s *dev, uint8_t pin, + int option, void *val) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)dev; + uint16_t cfgset = 0; + int ret = 0; + + if (option == IOEXPANDER_OPTION_INTCFG) + { + switch ((uintptr_t)val) + { + case IOEXPANDER_VAL_FALLING: + cfgset = GPIOCHIP_LINE_FLAG_FALLING; + break; + + case IOEXPANDER_VAL_RISING: + cfgset = GPIOCHIP_LINE_FLAG_RISING; + break; + + case IOEXPANDER_VAL_BOTH: + cfgset = GPIOCHIP_LINE_FLAG_BOTH; + break; + + case IOEXPANDER_VAL_DISABLE: + cfgset = GPIOCHIP_LINE_FLAG_DISABLE; + break; + + default: + return -ENOTSUP; + } + + ret = host_gpiochip_irq_request(priv->dev, pin, cfgset); + if (ret < 0) + { + gpioerr("ERROR: Failed to request event: %s\n", strerror(errno)); + } + } + else + { + gpioinfo("gpiochip io option not support\n"); + return -ENOTSUP; + } + + return ret; +} + +/**************************************************************************** + * Name: sim_gpiochip_writepin + * + * Description: + * Write gpiochip pin value. + * + * Input Parameters: + * dev - A pointer to instance of sim gpiochip device. + * pin - The pin number. + * value - The value write to the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +static int sim_gpiochip_writepin(struct ioexpander_dev_s *dev, uint8_t pin, + bool value) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)dev; + + return host_gpiochip_writepin(priv->dev, pin, value); +} + +/**************************************************************************** + * Name: sim_gpiochip_readpin + * + * Description: + * Read gpiochip pin value. + * + * Input Parameters: + * dev - A pointer to instance of sim gpiochip device. + * pin - The pin number. + * value - The value write to the pin. + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +static int sim_gpiochip_readpin(struct ioexpander_dev_s *dev, uint8_t pin, + bool *value) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)dev; + + return host_gpiochip_readpin(priv->dev, pin, value); +} + +#ifdef CONFIG_IOEXPANDER_INT_ENABLE + +/**************************************************************************** + * Name: sim_gpiochip_attach + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +static void *sim_gpiochip_attach(struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, + ioe_callback_t callback, + void *arg) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)dev; + void *handle = NULL; + int i; + + for (i = 0; i < CONFIG_IOEXPANDER_NPINS; i++) + { + if (pinset & (1 << i)) + { + priv->cb[i].cbarg = arg; + handle = &priv->cb[i]; + priv->cb[i].cbfunc = callback; + } + } + + return handle; +} + +/**************************************************************************** + * Name: sim_gpiochip_detach + * + * Returned Value: + * 0 for success, other for fail. + ****************************************************************************/ + +static int sim_gpiochip_detach(struct ioexpander_dev_s *dev, void *handle) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)dev; + struct sim_gpiochip_callback_s *cb = handle; + + if (priv == NULL || cb == NULL) + { + gpioerr("ERROR: Invalid handle\n"); + return -EINVAL; + } + + cb->cbfunc = NULL; + cb->cbarg = NULL; + return 0; +} +#endif + +/**************************************************************************** + * Name: sim_gpiochip_register_gpio + * + * Description: + * register gpio for gpiochip device + * + * Input Parameters: + * priv - A pointer to instance of sim gpiochip device. + * + * Returned Value: + * 0 for OK. + * + ****************************************************************************/ + +static int sim_gpiochip_register_gpio(struct sim_gpiochip_dev_s *priv) +{ + struct ioexpander_dev_s *ioe = (struct ioexpander_dev_s *)priv; + bool input; + int line; + int ret; + + for (line = 0; line < CONFIG_IOEXPANDER_NPINS; line++) + { + ret = host_gpiochip_get_line(priv->dev, line, &input); + if (ret != 0) + { + continue; + } + + if (input) + { + ret = gpio_lower_half(ioe, line, GPIO_INPUT_PIN, + GPIOCHIP_LINE_BASE + line); + } + else + { + ret = gpio_lower_half(ioe, line, GPIO_OUTPUT_PIN, + GPIOCHIP_LINE_BASE + line); + } + + if (ret < 0) + { + return ret; + } + } + + return 0; +} + +/**************************************************************************** + * Name: sim_gpiochip_irq_process + * + * Description: + * work to poll irq event for gpiochip device + * + * Input Parameters: + * priv - A pointer to instance of sim gpiochip device. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void sim_gpiochip_irq_process(struct sim_gpiochip_dev_s *priv) +{ + int line; + + for (line = 0; line < CONFIG_IOEXPANDER_NPINS; line++) + { + if (host_gpiochip_irq_active(priv->dev, line)) + { + if (priv->cb[line].cbfunc) + { + priv->cb[line].cbfunc((struct ioexpander_dev_s *)priv, + line, priv->cb[line].cbarg); + } + } + } +} + +/**************************************************************************** + * Name: sim_gpiochip_interrupt + * + * Description: + * wdog timer function for gpiochip device + * + * Input Parameters: + * arg - A pointer to instance of sim gpiochip device. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void sim_gpiochip_interrupt(wdparm_t arg) +{ + struct sim_gpiochip_dev_s *priv = (struct sim_gpiochip_dev_s *)arg; + + if (priv) + { + sim_gpiochip_irq_process(priv); + + wd_start(&priv->wdog, SIM_GPIOCHIP_WDOG_DELAY, + sim_gpiochip_interrupt, (wdparm_t)priv); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: sim_gpiochip_initialize + * + * Description: + * Initialize one gpiochip device + * + * Input Parameters: + * path - the name of gpiochip device in sim, e.g. "/dev/gpiochipN". + * + * Returned Value: + * The pointer to the instance of sim gpiochip device. + * + ****************************************************************************/ + +int sim_gpiochip_initialize(const char *path) +{ + struct sim_gpiochip_dev_s *priv; + int ret; + + priv = kmm_zalloc(sizeof(struct sim_gpiochip_dev_s)); + if (priv == NULL) + { + gpioerr("Failed to allocate memory for gpiochip device"); + return -ENOMEM; + } + + priv->ops = &g_sim_gpiochip_ops; + + priv->dev = host_gpiochip_alloc(path); + if (priv->dev == NULL) + { + gpioerr("Failed to init gpiochip: %d", ret); + kmm_free(priv); + return -ENODEV; + } + + ret = sim_gpiochip_register_gpio(priv); + if (ret < 0) + { + gpioerr("Failed to register gpio: %d", ret); + host_gpiochip_free(priv->dev); + kmm_free(priv); + return ret; + } + + wd_start(&priv->wdog, SIM_GPIOCHIP_WDOG_DELAY, + sim_gpiochip_interrupt, (wdparm_t)priv); + + return 0; +} diff --git a/arch/sim/src/sim/sim_gpiochip.h b/arch/sim/src/sim/sim_gpiochip.h new file mode 100644 index 00000000000..43bdf3b672f --- /dev/null +++ b/arch/sim/src/sim/sim_gpiochip.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * arch/sim/src/sim/sim_gpiochip.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __ARCH_SIM_SRC_SIM_GPIOCHIP_H +#define __ARCH_SIM_SRC_SIM_GPIOCHIP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#ifdef __SIM__ +# include "config.h" +#endif + +#include <stdint.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define GPIOCHIP_LINE_FLAG_DISABLE (1 << 0) +#define GPIOCHIP_LINE_FLAG_RISING (1 << 1) +#define GPIOCHIP_LINE_FLAG_FALLING (1 << 2) +#define GPIOCHIP_LINE_FLAG_BOTH (1 << 3) + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +struct host_gpiochip_dev +{ + int file; + int line_fd[CONFIG_IOEXPANDER_NPINS]; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct host_gpiochip_dev *host_gpiochip_alloc(const char *filename); +void host_gpiochip_free(struct host_gpiochip_dev *dev); +int host_gpiochip_get_line(struct host_gpiochip_dev *priv, uint8_t pin, + bool *input); +int host_gpiochip_readpin(struct host_gpiochip_dev *dev, + uint8_t line, bool *value); +int host_gpiochip_writepin(struct host_gpiochip_dev *dev, + uint8_t pin, bool value); +int host_gpiochip_direction(struct host_gpiochip_dev *dev, + uint8_t pin, bool input); +int host_gpiochip_irq_request(struct host_gpiochip_dev *dev, uint8_t pin, + uint16_t cfgset); +bool host_gpiochip_irq_active(struct host_gpiochip_dev *dev, uint8_t pin); + +#endif /* __ARCH_SIM_SRC_SIM_GPIOCHIP_H */ diff --git a/arch/sim/src/sim/sim_internal.h b/arch/sim/src/sim/sim_internal.h index 45d74064852..f278222772f 100644 --- a/arch/sim/src/sim/sim_internal.h +++ b/arch/sim/src/sim/sim_internal.h @@ -516,5 +516,9 @@ size_t sim_stack_check(void *alloc, size_t size); void sim_stack_color(void *stackbase, size_t nbytes); #endif +#ifdef CONFIG_SIM_GPIOCHIP +int sim_gpiochip_initialize(const char *filename); +#endif + #endif /* __ASSEMBLY__ */ #endif /* __ARCH_SIM_SRC_SIM_INTERNAL_H */
