blob: 014475c7918a0359983a8b236cc29a28f12c7376 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
* This file is part of libgpiod.
*
* Copyright (C) 2017-2018 Bartosz Golaszewski <bartekgola@gmail.com>
*/
/* Implementation of the high-level API. */
#include <errno.h>
#include <gpiod.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
static int ctxless_flags_to_line_request_flags(bool active_low, int flags)
{
int req_flags = 0;
if (active_low)
req_flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
if (flags & GPIOD_CTXLESS_FLAG_OPEN_DRAIN)
req_flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
if (flags & GPIOD_CTXLESS_FLAG_OPEN_SOURCE)
req_flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
if (flags & GPIOD_CTXLESS_FLAG_BIAS_DISABLE)
req_flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE;
if (flags & GPIOD_CTXLESS_FLAG_BIAS_PULL_UP)
req_flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
if (flags & GPIOD_CTXLESS_FLAG_BIAS_PULL_DOWN)
req_flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
return req_flags;
}
int gpiod_ctxless_get_value(const char *device, unsigned int offset,
bool active_low, const char *consumer)
{
int value, rv;
rv = gpiod_ctxless_get_value_multiple(device, &offset, &value,
1, active_low, consumer);
if (rv < 0)
return rv;
return value;
}
int gpiod_ctxless_get_value_ext(const char *device, unsigned int offset,
bool active_low, const char *consumer,
int flags)
{
int value, rv;
rv = gpiod_ctxless_get_value_multiple_ext(device, &offset, &value, 1,
active_low, consumer, flags);
if (rv < 0)
return rv;
return value;
}
int gpiod_ctxless_get_value_multiple(const char *device,
const unsigned int *offsets, int *values,
unsigned int num_lines, bool active_low,
const char *consumer)
{
int rv;
rv = gpiod_ctxless_get_value_multiple_ext(device, offsets, values,
num_lines, active_low,
consumer, 0);
return rv;
}
int gpiod_ctxless_get_value_multiple_ext(const char *device,
const unsigned int *offsets,
int *values, unsigned int num_lines,
bool active_low,
const char *consumer, int flags)
{
struct gpiod_line_bulk bulk;
struct gpiod_chip *chip;
struct gpiod_line *line;
unsigned int i;
int rv, req_flags;
if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) {
errno = EINVAL;
return -1;
}
chip = gpiod_chip_open_lookup(device);
if (!chip)
return -1;
gpiod_line_bulk_init(&bulk);
for (i = 0; i < num_lines; i++) {
line = gpiod_chip_get_line(chip, offsets[i]);
if (!line) {
gpiod_chip_close(chip);
return -1;
}
gpiod_line_bulk_add(&bulk, line);
}
req_flags = ctxless_flags_to_line_request_flags(active_low, flags);
rv = gpiod_line_request_bulk_input_flags(&bulk, consumer, req_flags);
if (rv < 0) {
gpiod_chip_close(chip);
return -1;
}
memset(values, 0, sizeof(*values) * num_lines);
rv = gpiod_line_get_value_bulk(&bulk, values);
gpiod_chip_close(chip);
return rv;
}
int gpiod_ctxless_set_value(const char *device, unsigned int offset, int value,
bool active_low, const char *consumer,
gpiod_ctxless_set_value_cb cb, void *data)
{
return gpiod_ctxless_set_value_multiple(device, &offset, &value, 1,
active_low, consumer, cb, data);
}
int gpiod_ctxless_set_value_ext(const char *device, unsigned int offset,
int value, bool active_low,
const char *consumer,
gpiod_ctxless_set_value_cb cb,
void *data, int flags)
{
return gpiod_ctxless_set_value_multiple_ext(device, &offset, &value,
1, active_low, consumer,
cb, data, flags);
}
int gpiod_ctxless_set_value_multiple(const char *device,
const unsigned int *offsets,
const int *values, unsigned int num_lines,
bool active_low, const char *consumer,
gpiod_ctxless_set_value_cb cb, void *data)
{
return gpiod_ctxless_set_value_multiple_ext(device, offsets, values,
num_lines, active_low,
consumer, cb, data, 0);
}
int gpiod_ctxless_set_value_multiple_ext(
const char *device, const unsigned int *offsets,
const int *values, unsigned int num_lines,
bool active_low, const char *consumer,
gpiod_ctxless_set_value_cb cb, void *data, int flags)
{
struct gpiod_line_bulk bulk;
struct gpiod_chip *chip;
struct gpiod_line *line;
unsigned int i;
int rv, req_flags;
if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) {
errno = EINVAL;
return -1;
}
chip = gpiod_chip_open_lookup(device);
if (!chip)
return -1;
gpiod_line_bulk_init(&bulk);
for (i = 0; i < num_lines; i++) {
line = gpiod_chip_get_line(chip, offsets[i]);
if (!line) {
gpiod_chip_close(chip);
return -1;
}
gpiod_line_bulk_add(&bulk, line);
}
req_flags = ctxless_flags_to_line_request_flags(active_low, flags);
rv = gpiod_line_request_bulk_output_flags(&bulk, consumer,
req_flags, values);
if (rv < 0) {
gpiod_chip_close(chip);
return -1;
}
if (cb)
cb(data);
gpiod_chip_close(chip);
return 0;
}
static int basic_event_poll(unsigned int num_lines,
struct gpiod_ctxless_event_poll_fd *fds,
const struct timespec *timeout,
void *data GPIOD_UNUSED)
{
struct pollfd poll_fds[GPIOD_LINE_BULK_MAX_LINES];
unsigned int i;
int rv, ret;
if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES)
return GPIOD_CTXLESS_EVENT_POLL_RET_ERR;
memset(poll_fds, 0, sizeof(poll_fds));
for (i = 0; i < num_lines; i++) {
poll_fds[i].fd = fds[i].fd;
poll_fds[i].events = POLLIN | POLLPRI;
}
rv = ppoll(poll_fds, num_lines, timeout, NULL);
if (rv < 0) {
if (errno == EINTR)
return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT;
else
return GPIOD_CTXLESS_EVENT_POLL_RET_ERR;
} else if (rv == 0) {
return GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT;
}
ret = rv;
for (i = 0; i < num_lines; i++) {
if (poll_fds[i].revents) {
fds[i].event = true;
if (!--rv)
break;
}
}
return ret;
}
int gpiod_ctxless_event_loop(const char *device, unsigned int offset,
bool active_low, const char *consumer,
const struct timespec *timeout,
gpiod_ctxless_event_poll_cb poll_cb,
gpiod_ctxless_event_handle_cb event_cb,
void *data)
{
return gpiod_ctxless_event_monitor(device,
GPIOD_CTXLESS_EVENT_BOTH_EDGES,
offset, active_low, consumer,
timeout, poll_cb, event_cb, data);
}
int gpiod_ctxless_event_loop_multiple(const char *device,
const unsigned int *offsets,
unsigned int num_lines, bool active_low,
const char *consumer,
const struct timespec *timeout,
gpiod_ctxless_event_poll_cb poll_cb,
gpiod_ctxless_event_handle_cb event_cb,
void *data)
{
return gpiod_ctxless_event_monitor_multiple(
device, GPIOD_CTXLESS_EVENT_BOTH_EDGES,
offsets, num_lines, active_low, consumer,
timeout, poll_cb, event_cb, data);
}
int gpiod_ctxless_event_monitor(const char *device, int event_type,
unsigned int offset, bool active_low,
const char *consumer,
const struct timespec *timeout,
gpiod_ctxless_event_poll_cb poll_cb,
gpiod_ctxless_event_handle_cb event_cb,
void *data)
{
return gpiod_ctxless_event_monitor_multiple(device, event_type,
&offset, 1, active_low,
consumer, timeout,
poll_cb, event_cb, data);
}
int gpiod_ctxless_event_monitor_ext(const char *device, int event_type,
unsigned int offset, bool active_low,
const char *consumer,
const struct timespec *timeout,
gpiod_ctxless_event_poll_cb poll_cb,
gpiod_ctxless_event_handle_cb event_cb,
void *data, int flags)
{
return gpiod_ctxless_event_monitor_multiple_ext(
device, event_type, &offset, 1, active_low,
consumer, timeout, poll_cb, event_cb, data, flags);
}
int gpiod_ctxless_event_monitor_multiple(
const char *device, int event_type,
const unsigned int *offsets,
unsigned int num_lines, bool active_low,
const char *consumer,
const struct timespec *timeout,
gpiod_ctxless_event_poll_cb poll_cb,
gpiod_ctxless_event_handle_cb event_cb,
void *data)
{
return gpiod_ctxless_event_monitor_multiple_ext(
device, event_type, offsets,
num_lines, active_low, consumer, timeout,
poll_cb, event_cb, data, 0);
}
int gpiod_ctxless_event_monitor_multiple_ext(
const char *device, int event_type,
const unsigned int *offsets,
unsigned int num_lines, bool active_low,
const char *consumer, const struct timespec *timeout,
gpiod_ctxless_event_poll_cb poll_cb,
gpiod_ctxless_event_handle_cb event_cb,
void *data, int flags)
{
struct gpiod_ctxless_event_poll_fd fds[GPIOD_LINE_BULK_MAX_LINES];
struct gpiod_line_request_config conf;
struct gpiod_line_event event;
struct gpiod_line_bulk bulk;
int rv, ret, evtype, cnt;
struct gpiod_chip *chip;
struct gpiod_line *line;
unsigned int i;
if (!num_lines || num_lines > GPIOD_LINE_BULK_MAX_LINES) {
errno = EINVAL;
return -1;
}
if (!poll_cb)
poll_cb = basic_event_poll;
chip = gpiod_chip_open_lookup(device);
if (!chip)
return -1;
gpiod_line_bulk_init(&bulk);
for (i = 0; i < num_lines; i++) {
line = gpiod_chip_get_line(chip, offsets[i]);
if (!line) {
gpiod_chip_close(chip);
return -1;
}
gpiod_line_bulk_add(&bulk, line);
}
conf.flags = ctxless_flags_to_line_request_flags(active_low, flags);
conf.consumer = consumer;
if (event_type == GPIOD_CTXLESS_EVENT_RISING_EDGE) {
conf.request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
} else if (event_type == GPIOD_CTXLESS_EVENT_FALLING_EDGE) {
conf.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
} else if (event_type == GPIOD_CTXLESS_EVENT_BOTH_EDGES) {
conf.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
} else {
errno = -EINVAL;
ret = -1;
goto out;
}
rv = gpiod_line_request_bulk(&bulk, &conf, NULL);
if (rv) {
ret = -1;
goto out;
}
memset(fds, 0, sizeof(fds));
for (i = 0; i < num_lines; i++) {
line = gpiod_line_bulk_get_line(&bulk, i);
fds[i].fd = gpiod_line_event_get_fd(line);
}
for (;;) {
for (i = 0; i < num_lines; i++)
fds[i].event = false;
cnt = poll_cb(num_lines, fds, timeout, data);
if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_ERR) {
ret = -1;
goto out;
} else if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_TIMEOUT) {
rv = event_cb(GPIOD_CTXLESS_EVENT_CB_TIMEOUT,
0, &event.ts, data);
if (rv == GPIOD_CTXLESS_EVENT_CB_RET_ERR) {
ret = -1;
goto out;
} else if (rv == GPIOD_CTXLESS_EVENT_CB_RET_STOP) {
ret = 0;
goto out;
}
} else if (cnt == GPIOD_CTXLESS_EVENT_POLL_RET_STOP) {
ret = 0;
goto out;
}
for (i = 0; i < num_lines; i++) {
if (!fds[i].event)
continue;
line = gpiod_line_bulk_get_line(&bulk, i);
rv = gpiod_line_event_read(line, &event);
if (rv < 0) {
ret = rv;
goto out;
}
if (event.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
evtype = GPIOD_CTXLESS_EVENT_CB_RISING_EDGE;
else
evtype = GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE;
rv = event_cb(evtype, gpiod_line_offset(line),
&event.ts, data);
if (rv == GPIOD_CTXLESS_EVENT_CB_RET_ERR) {
ret = -1;
goto out;
} else if (rv == GPIOD_CTXLESS_EVENT_CB_RET_STOP) {
ret = 0;
goto out;
}
if (!--cnt)
break;
}
}
out:
gpiod_chip_close(chip);
return ret;
}
int gpiod_ctxless_find_line(const char *name, char *chipname,
size_t chipname_size, unsigned int *offset)
{
struct gpiod_chip *chip;
struct gpiod_line *line;
line = gpiod_line_find(name);
if (!line) {
if (errno == ENOENT)
return 0;
else
return -1;
}
chip = gpiod_line_get_chip(line);
snprintf(chipname, chipname_size, "%s", gpiod_chip_name(chip));
*offset = gpiod_line_offset(line);
gpiod_chip_close(chip);
return 1;
}