From ecbd5aa85ac303897f454c956b53d2c0c2c5c7c5 Mon Sep 17 00:00:00 2001 From: "Vanya A. Sergeev" Date: Wed, 20 May 2020 02:03:34 -0500 Subject: [PATCH] gpio: add getters and setters for bias, drive, inverted resolves #23. --- docs/gpio.md | 31 ++++- src/gpio.c | 273 ++++++++++++++++++++++++++++++++++++---- src/gpio.h | 19 +++ tests/test_gpio.c | 69 ++++++++++ tests/test_gpio_sysfs.c | 18 +++ 5 files changed, 386 insertions(+), 24 deletions(-) diff --git a/docs/gpio.md b/docs/gpio.md index 1b66efc..2975256 100644 --- a/docs/gpio.md +++ b/docs/gpio.md @@ -27,10 +27,16 @@ int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios /* Getters */ int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); /* Setters */ int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t edge); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); /* Miscellaneous Properties */ unsigned int gpio_line(gpio_t *gpio); @@ -61,6 +67,17 @@ const char *gpio_errmsg(gpio_t *gpio); * `GPIO_EDGE_FALLING`: Falling edge (1 -> 0 transition) * `GPIO_EDGE_BOTH`: Both edges (X -> !X transition) +* `gpio_bias_t` + * `GPIO_BIAS_DEFAULT`: Default line bias + * `GPIO_BIAS_PULL_UP`: Pull-up + * `GPIO_BIAS_PULL_DOWN`: Pull-down + * `GPIO_BIAS_DISABLE`: Disable line bias + +* `gpio_drive_t` + * `GPIO_DRIVE_DEFAULT`: Default line drive (push-pull) + * `GPIO_DRIVE_OPEN_DRAIN`: Open drain + * `GPIO_DRIVE_OPEN_SOURCE`: Open source + ### DESCRIPTION ``` c @@ -188,8 +205,13 @@ Free a GPIO handle. ```c int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); ``` -Get the configured direction or interrupt edge, respectively, of the GPIO. +Get the configured direction, interrupt edge, line bias, line drive, inverted (active low) properties, respectively, of the GPIO. + +Line bias and line drive properties are not supported by sysfs GPIOs. `gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. @@ -200,8 +222,13 @@ Returns 0 on success, or a negative [GPIO error code](#return-value) on failure. ```c int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t edge); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); ``` -Set the direction or interrupt edge, respectively, of the GPIO. +Set the direction, interrupt edge, line bias, line drive, inverted (active low) properties, respectively, of the GPIO. + +Line bias and line drive properties are not supported by sysfs GPIOs. `gpio` should be a valid pointer to a GPIO handle opened with one of the `gpio_open*()` functions. diff --git a/src/gpio.c b/src/gpio.c index c5b47b4..20fe36d 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -34,8 +34,14 @@ struct gpio_ops { int (*close)(gpio_t *gpio); int (*get_direction)(gpio_t *gpio, gpio_direction_t *direction); int (*get_edge)(gpio_t *gpio, gpio_edge_t *edge); + int (*get_bias)(gpio_t *gpio, gpio_bias_t *bias); + int (*get_drive)(gpio_t *gpio, gpio_drive_t *drive); + int (*get_inverted)(gpio_t *gpio, bool *inverted); int (*set_direction)(gpio_t *gpio, gpio_direction_t direction); int (*set_edge)(gpio_t *gpio, gpio_edge_t edge); + int (*set_bias)(gpio_t *gpio, gpio_bias_t bias); + int (*set_drive)(gpio_t *gpio, gpio_drive_t drive); + int (*set_inverted)(gpio_t *gpio, bool inverted); unsigned int (*line)(gpio_t *gpio); int (*fd)(gpio_t *gpio); int (*name)(gpio_t *gpio, char *str, size_t len); @@ -56,6 +62,10 @@ struct gpio_handle { int chip_fd; gpio_direction_t direction; gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; + char label[32]; } cdev; struct { unsigned int line; @@ -130,6 +140,18 @@ int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge) { return gpio->ops->get_edge(gpio, edge); } +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + return gpio->ops->get_bias(gpio, bias); +} + +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + return gpio->ops->get_drive(gpio, drive); +} + +int gpio_get_inverted(gpio_t *gpio, bool *inverted) { + return gpio->ops->get_inverted(gpio, inverted); +} + int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction) { return gpio->ops->set_direction(gpio, direction); } @@ -138,6 +160,18 @@ int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge) { return gpio->ops->set_edge(gpio, edge); } +int gpio_set_bias(gpio_t *gpio, gpio_bias_t bias) { + return gpio->ops->set_bias(gpio, bias); +} + +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive) { + return gpio->ops->set_drive(gpio, drive); +} + +int gpio_set_inverted(gpio_t *gpio, bool inverted) { + return gpio->ops->set_inverted(gpio, inverted); +} + unsigned int gpio_line(gpio_t *gpio) { return gpio->ops->line(gpio); } @@ -479,6 +513,77 @@ static int gpio_sysfs_get_edge(gpio_t *gpio, gpio_edge_t *edge) { return 0; } +static int gpio_sysfs_set_bias(gpio_t *gpio, gpio_bias_t bias) { + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line bias attribute"); +} + +static int gpio_sysfs_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line bias attribute"); +} + +static int gpio_sysfs_set_drive(gpio_t *gpio, gpio_drive_t drive) { + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line drive attribute"); +} + +static int gpio_sysfs_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "GPIO of type sysfs does not support line drive attribute"); +} + +static int gpio_sysfs_set_inverted(gpio_t *gpio, bool inverted) { + char gpio_path[P_PATH_MAX]; + static const char *inverted_str[2] = {"0\n", "1\n"}; + int fd; + + /* Write active_low */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/active_low", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_WRONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Opening GPIO 'active_low'"); + + if (write(fd, inverted_str[inverted], strlen(inverted_str[inverted])) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errsv, "Writing GPIO 'active_low'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_CONFIGURE, errno, "Closing GPIO 'active_low'"); + + return 0; +} + +static int gpio_sysfs_get_inverted(gpio_t *gpio, bool *inverted) { + char gpio_path[P_PATH_MAX]; + char buf[4]; + int fd, ret; + + /* Read active_low */ + snprintf(gpio_path, sizeof(gpio_path), "/sys/class/gpio/gpio%u/active_low", gpio->u.sysfs.line); + + if ((fd = open(gpio_path, O_RDONLY)) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Opening GPIO 'active_low'"); + + if ((ret = read(fd, buf, sizeof(buf))) < 0) { + int errsv = errno; + close(fd); + return _gpio_error(gpio, GPIO_ERROR_QUERY, errsv, "Reading GPIO 'active_low'"); + } + + if (close(fd) < 0) + return _gpio_error(gpio, GPIO_ERROR_QUERY, errno, "Closing GPIO 'active_low'"); + + buf[ret] = '\0'; + + if (buf[0] == '0') + *inverted = false; + else if (buf[0] == '1') + *inverted = true; + else + return _gpio_error(gpio, GPIO_ERROR_QUERY, 0, "Unknown GPIO active_low value"); + + return 0; +} + static unsigned int gpio_sysfs_line(gpio_t *gpio) { return gpio->u.sysfs.line; } @@ -560,6 +665,8 @@ static int gpio_sysfs_tostring(gpio_t *gpio, char *str, size_t len) { const char *direction_str; gpio_edge_t edge; const char *edge_str; + bool inverted; + const char *inverted_str; char chip_name[32]; const char *chip_name_str; char chip_label[32]; @@ -579,6 +686,11 @@ static int gpio_sysfs_tostring(gpio_t *gpio, char *str, size_t len) { (edge == GPIO_EDGE_FALLING) ? "falling" : (edge == GPIO_EDGE_BOTH) ? "both" : "unknown"; + if (gpio_sysfs_get_inverted(gpio, &inverted) < 0) + inverted_str = ""; + else + inverted_str = inverted ? "true" : "false"; + if (gpio_sysfs_chip_name(gpio, chip_name, sizeof(chip_name)) < 0) chip_name_str = ""; else @@ -589,8 +701,8 @@ static int gpio_sysfs_tostring(gpio_t *gpio, char *str, size_t len) { else chip_label_str = chip_label; - return snprintf(str, len, "GPIO %u (fd=%d, direction=%s, edge=%s, chip_name=\"%s\", chip_label=\"%s\", type=sysfs)", - gpio->u.sysfs.line, gpio->u.sysfs.line_fd, direction_str, edge_str, chip_name_str, chip_label_str); + return snprintf(str, len, "GPIO %u (fd=%d, direction=%s, edge=%s, inverted=%s, chip_name=\"%s\", chip_label=\"%s\", type=sysfs)", + gpio->u.sysfs.line, gpio->u.sysfs.line_fd, direction_str, edge_str, inverted_str, chip_name_str, chip_label_str); } static const struct gpio_ops gpio_sysfs_ops = { @@ -601,8 +713,14 @@ static const struct gpio_ops gpio_sysfs_ops = { .close = gpio_sysfs_close, .get_direction = gpio_sysfs_get_direction, .get_edge = gpio_sysfs_get_edge, + .get_bias = gpio_sysfs_get_bias, + .get_drive = gpio_sysfs_get_drive, + .get_inverted = gpio_sysfs_get_inverted, .set_direction = gpio_sysfs_set_direction, .set_edge = gpio_sysfs_set_edge, + .set_bias = gpio_sysfs_set_bias, + .set_drive = gpio_sysfs_set_drive, + .set_inverted = gpio_sysfs_set_inverted, .line = gpio_sysfs_line, .fd = gpio_sysfs_fd, .name = gpio_sysfs_name, @@ -713,12 +831,43 @@ int gpio_open_sysfs(gpio_t *gpio, unsigned int line, gpio_direction_t direction) /* cdev implementation */ /*********************************************************************************/ -static int _gpio_cdev_reopen(gpio_t *gpio, gpio_direction_t direction, gpio_edge_t edge) { - static const char gpio_label[] = "periphery"; +static int _gpio_cdev_reopen(gpio_t *gpio, gpio_direction_t direction, gpio_edge_t edge, gpio_bias_t bias, gpio_drive_t drive, bool inverted) { + uint32_t flags = 0; + + #ifdef GPIOHANDLE_REQUEST_BIAS_PULL_UP + if (bias == GPIO_BIAS_PULL_UP) + flags |= GPIOHANDLE_REQUEST_BIAS_PULL_UP; + else if (bias == GPIO_BIAS_PULL_DOWN) + flags |= GPIOHANDLE_REQUEST_BIAS_PULL_DOWN; + else if (bias == GPIO_BIAS_DISABLE) + flags |= GPIOHANDLE_REQUEST_BIAS_DISABLE; + #else + if (bias != GPIO_BIAS_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "Kernel version does not support configuring GPIO line bias"); + #endif + + #ifdef GPIOHANDLE_REQUEST_OPEN_DRAIN + if (drive == GPIO_DRIVE_OPEN_DRAIN) + flags |= GPIOHANDLE_REQUEST_OPEN_DRAIN; + else if (drive == GPIO_DRIVE_OPEN_SOURCE) + flags |= GPIOHANDLE_REQUEST_OPEN_SOURCE; + #else + if (drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_UNSUPPORTED, 0, "Kernel version does not support configuring GPIO line drive"); + #endif + + if (inverted) + flags |= GPIOHANDLE_REQUEST_ACTIVE_LOW; + + /* FIXME this should really use GPIOHANDLE_SET_CONFIG_IOCTL instead of + * closing and reopening, especially to preserve output value on + * configuration changes */ if (gpio->u.cdev.line_fd >= 0) { if (close(gpio->u.cdev.line_fd) < 0) return _gpio_error(gpio, GPIO_ERROR_CLOSE, errno, "Closing GPIO line"); + + gpio->u.cdev.line_fd = -1; } if (direction == GPIO_DIR_IN) { @@ -726,51 +875,52 @@ static int _gpio_cdev_reopen(gpio_t *gpio, gpio_direction_t direction, gpio_edge struct gpiohandle_request request = {0}; request.lineoffsets[0] = gpio->u.cdev.line; - request.flags = GPIOHANDLE_REQUEST_INPUT; - strncpy(request.consumer_label, gpio_label, sizeof(request.consumer_label)); + request.flags = flags | GPIOHANDLE_REQUEST_INPUT; + strncpy(request.consumer_label, gpio->u.cdev.label, sizeof(request.consumer_label)); request.lines = 1; if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &request) < 0) return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening input line handle"); gpio->u.cdev.line_fd = request.fd; - gpio->u.cdev.direction = GPIO_DIR_IN; - gpio->u.cdev.edge = GPIO_EDGE_NONE; } else { struct gpioevent_request request = {0}; request.lineoffset = gpio->u.cdev.line; - request.handleflags = GPIOHANDLE_REQUEST_INPUT; + request.handleflags = flags | GPIOHANDLE_REQUEST_INPUT; request.eventflags = (edge == GPIO_EDGE_RISING) ? GPIOEVENT_REQUEST_RISING_EDGE : (edge == GPIO_EDGE_FALLING) ? GPIOEVENT_REQUEST_FALLING_EDGE : GPIOEVENT_REQUEST_BOTH_EDGES; - strncpy(request.consumer_label, gpio_label, sizeof(request.consumer_label)); + strncpy(request.consumer_label, gpio->u.cdev.label, sizeof(request.consumer_label)); if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEEVENT_IOCTL, &request) < 0) return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening input event line handle"); gpio->u.cdev.line_fd = request.fd; - gpio->u.cdev.direction = GPIO_DIR_IN; - gpio->u.cdev.edge = edge; } } else { struct gpiohandle_request request = {0}; bool initial_value = (direction == GPIO_DIR_OUT_HIGH) ? true : false; + initial_value ^= inverted; request.lineoffsets[0] = gpio->u.cdev.line; - request.flags = GPIOHANDLE_REQUEST_OUTPUT; + request.flags = flags | GPIOHANDLE_REQUEST_OUTPUT; request.default_values[0] = initial_value; - strncpy(request.consumer_label, gpio_label, sizeof(request.consumer_label)); + strncpy(request.consumer_label, gpio->u.cdev.label, sizeof(request.consumer_label)); request.lines = 1; if (ioctl(gpio->u.cdev.chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &request) < 0) return _gpio_error(gpio, GPIO_ERROR_OPEN, errno, "Opening output line handle"); gpio->u.cdev.line_fd = request.fd; - gpio->u.cdev.direction = GPIO_DIR_OUT; - gpio->u.cdev.edge = GPIO_EDGE_NONE; } + gpio->u.cdev.direction = (direction == GPIO_DIR_IN) ? GPIO_DIR_IN : GPIO_DIR_OUT; + gpio->u.cdev.edge = edge; + gpio->u.cdev.bias = bias; + gpio->u.cdev.drive = drive; + gpio->u.cdev.inverted = inverted; + return 0; } @@ -857,6 +1007,21 @@ static int gpio_cdev_get_edge(gpio_t *gpio, gpio_edge_t *edge) { return 0; } +static int gpio_cdev_get_bias(gpio_t *gpio, gpio_bias_t *bias) { + *bias = gpio->u.cdev.bias; + return 0; +} + +static int gpio_cdev_get_drive(gpio_t *gpio, gpio_drive_t *drive) { + *drive = gpio->u.cdev.drive; + return 0; +} + +static int gpio_cdev_get_inverted(gpio_t *gpio, bool *inverted) { + *inverted = gpio->u.cdev.inverted; + return 0; +} + static int gpio_cdev_set_direction(gpio_t *gpio, gpio_direction_t direction) { if (direction != GPIO_DIR_IN && direction != GPIO_DIR_OUT && direction != GPIO_DIR_OUT_LOW && direction != GPIO_DIR_OUT_HIGH) return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO direction (can be in, out, low, high)"); @@ -864,7 +1029,7 @@ static int gpio_cdev_set_direction(gpio_t *gpio, gpio_direction_t direction) { if (gpio->u.cdev.direction == direction) return 0; - return _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE); + return _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE, gpio->u.cdev.bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); } static int gpio_cdev_set_edge(gpio_t *gpio, gpio_edge_t edge) { @@ -877,7 +1042,37 @@ static int gpio_cdev_set_edge(gpio_t *gpio, gpio_edge_t edge) { if (gpio->u.cdev.edge == edge) return 0; - return _gpio_cdev_reopen(gpio, GPIO_DIR_IN, edge); + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, edge, gpio->u.cdev.bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_bias(gpio_t *gpio, gpio_bias_t bias) { + if (bias != GPIO_BIAS_DEFAULT && bias != GPIO_BIAS_PULL_UP && bias != GPIO_BIAS_PULL_DOWN && bias != GPIO_BIAS_DISABLE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line bias (can be default, pull_up, pull_down, disable)"); + + if (gpio->u.cdev.bias == bias) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, bias, gpio->u.cdev.drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_drive(gpio_t *gpio, gpio_drive_t drive) { + if (drive != GPIO_DRIVE_DEFAULT && drive != GPIO_DRIVE_OPEN_DRAIN && drive != GPIO_DRIVE_OPEN_SOURCE) + return _gpio_error(gpio, GPIO_ERROR_ARG, 0, "Invalid GPIO line drive (can be default, open_drain, open_source)"); + + if (gpio->u.cdev.direction != GPIO_DIR_OUT && drive != GPIO_DRIVE_DEFAULT) + return _gpio_error(gpio, GPIO_ERROR_INVALID_OPERATION, 0, "Invalid operation: cannot set line drive on input GPIO"); + + if (gpio->u.cdev.drive == drive) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, gpio->u.cdev.bias, drive, gpio->u.cdev.inverted); +} + +static int gpio_cdev_set_inverted(gpio_t *gpio, bool inverted) { + if (gpio->u.cdev.inverted == inverted) + return 0; + + return _gpio_cdev_reopen(gpio, gpio->u.cdev.direction, gpio->u.cdev.edge, gpio->u.cdev.bias, gpio->u.cdev.drive, inverted); } static unsigned int gpio_cdev_line(gpio_t *gpio) { @@ -949,6 +1144,12 @@ static int gpio_cdev_tostring(gpio_t *gpio, char *str, size_t len) { const char *direction_str; gpio_edge_t edge; const char *edge_str; + gpio_bias_t bias; + const char *bias_str; + gpio_drive_t drive; + const char *drive_str; + bool inverted; + const char *inverted_str; char line_name[32]; const char *line_name_str; char line_label[32]; @@ -972,6 +1173,26 @@ static int gpio_cdev_tostring(gpio_t *gpio, char *str, size_t len) { (edge == GPIO_EDGE_FALLING) ? "falling" : (edge == GPIO_EDGE_BOTH) ? "both" : "unknown"; + if (gpio_cdev_get_bias(gpio, &bias) < 0) + bias_str = ""; + else + bias_str = (bias == GPIO_BIAS_DEFAULT) ? "default" : + (bias == GPIO_BIAS_PULL_UP) ? "pull_up" : + (bias == GPIO_BIAS_PULL_DOWN) ? "pull_down" : + (bias == GPIO_BIAS_DISABLE) ? "disable" : "unknown"; + + if (gpio_cdev_get_drive(gpio, &drive) < 0) + drive_str = ""; + else + drive_str = (drive == GPIO_DRIVE_DEFAULT) ? "default" : + (drive == GPIO_DRIVE_OPEN_DRAIN) ? "open_drain" : + (drive == GPIO_DRIVE_OPEN_SOURCE) ? "open_source" : "unknown"; + + if (gpio_cdev_get_inverted(gpio, &inverted) < 0) + inverted_str = ""; + else + inverted_str = inverted ? "true" : "false"; + if (gpio_cdev_name(gpio, line_name, sizeof(line_name)) < 0) line_name_str = ""; else @@ -992,8 +1213,8 @@ static int gpio_cdev_tostring(gpio_t *gpio, char *str, size_t len) { else chip_label_str = chip_label; - return snprintf(str, len, "GPIO %u (name=\"%s\", label=\"%s\", line_fd=%d, chip_fd=%d, direction=%s, edge=%s, chip_name=\"%s\", chip_label=\"%s\", type=cdev)", - gpio->u.cdev.line, line_name_str, line_label_str, gpio->u.cdev.line_fd, gpio->u.cdev.chip_fd, direction_str, edge_str, chip_name_str, chip_label_str); + return snprintf(str, len, "GPIO %u (name=\"%s\", label=\"%s\", line_fd=%d, chip_fd=%d, direction=%s, edge=%s, bias=%s, drive=%s, inverted=%s, chip_name=\"%s\", chip_label=\"%s\", type=cdev)", + gpio->u.cdev.line, line_name_str, line_label_str, gpio->u.cdev.line_fd, gpio->u.cdev.chip_fd, direction_str, edge_str, bias_str, drive_str, inverted_str, chip_name_str, chip_label_str); } static const struct gpio_ops gpio_cdev_ops = { @@ -1004,8 +1225,14 @@ static const struct gpio_ops gpio_cdev_ops = { .close = gpio_cdev_close, .get_direction = gpio_cdev_get_direction, .get_edge = gpio_cdev_get_edge, + .get_bias = gpio_cdev_get_bias, + .get_drive = gpio_cdev_get_drive, + .get_inverted = gpio_cdev_get_inverted, .set_direction = gpio_cdev_set_direction, .set_edge = gpio_cdev_set_edge, + .set_bias = gpio_cdev_set_bias, + .set_drive = gpio_cdev_set_drive, + .set_inverted = gpio_cdev_set_inverted, .line = gpio_cdev_line, .fd = gpio_cdev_fd, .name = gpio_cdev_name, @@ -1031,9 +1258,10 @@ int gpio_open(gpio_t *gpio, const char *path, unsigned int line, gpio_direction_ gpio->u.cdev.line = line; gpio->u.cdev.line_fd = -1; gpio->u.cdev.chip_fd = fd; + strncpy(gpio->u.cdev.label, "periphery", sizeof(gpio->u.cdev.label)); /* Open GPIO line */ - ret = _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE); + ret = _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE, GPIO_BIAS_DEFAULT, GPIO_DRIVE_DEFAULT, false); if (ret < 0) { close(fd); return ret; @@ -1089,9 +1317,10 @@ int gpio_open_name(gpio_t *gpio, const char *path, const char *name, gpio_direct gpio->u.cdev.line = line; gpio->u.cdev.line_fd = -1; gpio->u.cdev.chip_fd = fd; + strncpy(gpio->u.cdev.label, "periphery", sizeof(gpio->u.cdev.label)); /* Open GPIO line */ - ret = _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE); + ret = _gpio_cdev_reopen(gpio, direction, GPIO_EDGE_NONE, GPIO_BIAS_DEFAULT, GPIO_DRIVE_DEFAULT, false); if (ret < 0) { close(fd); return ret; diff --git a/src/gpio.h b/src/gpio.h index 12b0369..e9ae442 100644 --- a/src/gpio.h +++ b/src/gpio.h @@ -41,6 +41,19 @@ typedef enum gpio_edge { GPIO_EDGE_BOTH /* Both edges X -> !X */ } gpio_edge_t; +typedef enum gpio_bias { + GPIO_BIAS_DEFAULT, /* Default line bias */ + GPIO_BIAS_PULL_UP, /* Pull-up */ + GPIO_BIAS_PULL_DOWN, /* Pull-down */ + GPIO_BIAS_DISABLE, /* Disable line bias */ +} gpio_bias_t; + +typedef enum gpio_drive { + GPIO_DRIVE_DEFAULT, /* Default line drive (push-pull) */ + GPIO_DRIVE_OPEN_DRAIN, /* Open drain */ + GPIO_DRIVE_OPEN_SOURCE, /* Open source */ +} gpio_drive_t; + typedef struct gpio_handle gpio_t; /* Primary Functions */ @@ -63,10 +76,16 @@ int gpio_poll_multiple(gpio_t **gpios, size_t count, int timeout_ms, bool *gpios /* Getters */ int gpio_get_direction(gpio_t *gpio, gpio_direction_t *direction); int gpio_get_edge(gpio_t *gpio, gpio_edge_t *edge); +int gpio_get_bias(gpio_t *gpio, gpio_bias_t *bias); +int gpio_get_drive(gpio_t *gpio, gpio_drive_t *drive); +int gpio_get_inverted(gpio_t *gpio, bool *inverted); /* Setters */ int gpio_set_direction(gpio_t *gpio, gpio_direction_t direction); int gpio_set_edge(gpio_t *gpio, gpio_edge_t edge); +int gpio_set_bias(gpio_t *gpio, gpio_bias_t edge); +int gpio_set_drive(gpio_t *gpio, gpio_drive_t drive); +int gpio_set_inverted(gpio_t *gpio, bool inverted); /* Miscellaneous Properties */ unsigned int gpio_line(gpio_t *gpio); diff --git a/tests/test_gpio.c b/tests/test_gpio.c index 9c71e71..b66cad2 100644 --- a/tests/test_gpio.c +++ b/tests/test_gpio.c @@ -42,6 +42,9 @@ void test_open_config_close(void) { gpio_direction_t direction; gpio_edge_t edge; char label[32]; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; ptest(); @@ -69,6 +72,10 @@ void test_open_config_close(void) { passert(gpio_set_direction(gpio, 5) == GPIO_ERROR_ARG); /* Invalid interrupt edge */ passert(gpio_set_edge(gpio, 5) == GPIO_ERROR_ARG); + /* Invalid bias */ + passert(gpio_set_bias(gpio, 5) == GPIO_ERROR_ARG); + /* Invalid drive */ + passert(gpio_set_drive(gpio, 5) == GPIO_ERROR_ARG); /* Set direction out, check direction out, check value low */ passert(gpio_set_direction(gpio, GPIO_DIR_OUT) == 0); @@ -88,6 +95,29 @@ void test_open_config_close(void) { passert(direction == GPIO_DIR_OUT); passert(gpio_read(gpio, &value) == 0); passert(value == true); + + /* Set drive open drain, check drive open drain */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == 0); + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_OPEN_DRAIN); + /* Set drive open source, check drive open source */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_SOURCE) == 0); + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_OPEN_SOURCE); + /* Set drive default, check drive default */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_DEFAULT) == 0); + passert(gpio_get_drive(gpio, &drive) == 0); + passert(drive == GPIO_DRIVE_DEFAULT); + + /* Set inverted true, check inverted true */ + passert(gpio_set_inverted(gpio, true) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == true); + /* Set inverted false, check inverted false */ + passert(gpio_set_inverted(gpio, false) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == false); + /* Attempt to set interrupt edge on output GPIO */ passert(gpio_set_edge(gpio, GPIO_EDGE_RISING) == GPIO_ERROR_INVALID_OPERATION); /* Attempt to read event on output GPIO */ @@ -120,6 +150,26 @@ void test_open_config_close(void) { passert(gpio_get_edge(gpio, &edge) == 0); passert(edge == GPIO_EDGE_NONE); + /* Set bias pull up, check bias pull up */ + passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_UP) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_PULL_UP); + /* Set bias pull down, check bias pull down */ + passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_DOWN) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_PULL_DOWN); + /* Set bias disable, check bias disable */ + passert(gpio_set_bias(gpio, GPIO_BIAS_DISABLE) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_DISABLE); + /* Set bias default, check bias default */ + passert(gpio_set_bias(gpio, GPIO_BIAS_DEFAULT) == 0); + passert(gpio_get_bias(gpio, &bias) == 0); + passert(bias == GPIO_BIAS_DEFAULT); + + /* Attempt to set drive on input GPIO */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == GPIO_ERROR_INVALID_OPERATION); + /* Close GPIO */ passert(gpio_close(gpio) == 0); @@ -265,6 +315,25 @@ void test_loopback(void) { passert(gpio_close(gpio_in) == 0); passert(gpio_close(gpio_out) == 0); + /* Open both GPIOs as inputs */ + passert(gpio_open(gpio_in, device, pin_input, GPIO_DIR_IN) == 0); + passert(gpio_open(gpio_out, device, pin_output, GPIO_DIR_IN) == 0); + + /* Set bias pull-up, check value is high */ + passert(gpio_set_bias(gpio_in, GPIO_BIAS_PULL_UP) == 0); + usleep(1000); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == true); + + /* Set bias pull-down, check value is low */ + passert(gpio_set_bias(gpio_in, GPIO_BIAS_PULL_DOWN) == 0); + usleep(1000); + passert(gpio_read(gpio_in, &value) == 0); + passert(value == false); + + passert(gpio_close(gpio_in) == 0); + passert(gpio_close(gpio_out) == 0); + /* Free GPIO */ gpio_free(gpio_in); gpio_free(gpio_out); diff --git a/tests/test_gpio_sysfs.c b/tests/test_gpio_sysfs.c index 2887e78..26b9ce6 100644 --- a/tests/test_gpio_sysfs.c +++ b/tests/test_gpio_sysfs.c @@ -39,6 +39,9 @@ void test_open_config_close(void) { bool value; gpio_direction_t direction; gpio_edge_t edge; + gpio_bias_t bias; + gpio_drive_t drive; + bool inverted; ptest(); @@ -61,6 +64,12 @@ void test_open_config_close(void) { passert(gpio_set_direction(gpio, 5) == GPIO_ERROR_ARG); /* Invalid interrupt edge */ passert(gpio_set_edge(gpio, 5) == GPIO_ERROR_ARG); + /* Unsupported setting bias */ + passert(gpio_set_bias(gpio, GPIO_BIAS_PULL_UP) == GPIO_ERROR_UNSUPPORTED); + passert(gpio_get_bias(gpio, &bias) == GPIO_ERROR_UNSUPPORTED); + /* Unsupported setting drive */ + passert(gpio_set_drive(gpio, GPIO_DRIVE_OPEN_DRAIN) == GPIO_ERROR_UNSUPPORTED); + passert(gpio_get_drive(gpio, &drive) == GPIO_ERROR_UNSUPPORTED); /* Unsupported property */ passert(gpio_chip_fd(gpio) == GPIO_ERROR_UNSUPPORTED); /* Unsupported method */ @@ -85,6 +94,15 @@ void test_open_config_close(void) { passert(gpio_read(gpio, &value) == 0); passert(value == true); + /* Set inverted true, check inverted */ + passert(gpio_set_inverted(gpio, true) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == true); + /* Set inverted false, check inverted */ + passert(gpio_set_inverted(gpio, false) == 0); + passert(gpio_get_inverted(gpio, &inverted) == 0); + passert(inverted == false); + /* Set direction in, check direction in */ passert(gpio_set_direction(gpio, GPIO_DIR_IN) == 0); passert(gpio_get_direction(gpio, &direction) == 0);