/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple GPIO module
 *
 * 2022/05/11
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>

#include "lnxhal_gpio.h"

struct lnxhal_gpio * lnxhal_gpios_init(int gnum)
{
    int idx;
    struct lnxhal_gpio * gpio;
    struct lnxhal_gpio * gpios;

    if (gnum <= 0 || gnum > LNXHAL_GPIO_MAXNUM) {
        fprintf(stderr, "Error, invalid number of GPIO: %d\n", gnum);
        fflush(stderr);
        return NULL;
    }

    gpios = (struct lnxhal_gpio *) calloc(
        (size_t) gnum, sizeof(struct lnxhal_gpio));

    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        gpio->gpio_num = 0;
        gpio->gpio_fd = -1;
        gpio->gpio_val = 0;
        gpio->isout = 0;
        gpio->changed = 0;
    }
    return gpios;
}

static int lnxhal_gpio_reopen(struct lnxhal_gpio * gpio)
{
    size_t rl0;
    int gfd, errn;
    char gpath[128];
    const char * dirp;

    gfd = gpio->gpio_fd;
    if (gfd != -1) {
        close(gfd);
        gpio->gpio_fd = -1;
    }

    snprintf(gpath, sizeof(gpath), "/sys/class/gpio/gpio%d/direction", gpio->gpio_num);
    if (access(gpath, F_OK) == -1) {
        char gpio_num[24];
        int exfd, glen, error;

        error = 0;
        glen = snprintf(gpio_num, sizeof(gpio_num), "%d\n", gpio->gpio_num);
        exfd = open("/sys/class/gpio/export", O_WRONLY | O_TRUNC);
        if (exfd != -1) {
            if (glen <= 0 || write(exfd, gpio_num, (size_t) glen) != (ssize_t) glen)
                error = 1;
            close(exfd);
        } else
            error = 2;
        if (error || access(gpath, F_OK) == -1) {
            fprintf(stderr, "Error, failed to export GPIO %d: %d\n", gpio->gpio_num, error);
            fflush(stderr);
            return -1;
        }
    }
    gfd = open(gpath, O_WRONLY | O_CLOEXEC | O_TRUNC);
    if (gfd == -1) {
        errn = errno;
        fprintf(stderr, "Error, failed to open(%s): %s\n", gpath, strerror(errn));
        fflush(stderr);
        return -1;
    }

    dirp = gpio->isout ? "out\n" : "in\n";
    rl0 = strlen(dirp);
    if (write(gfd, dirp, rl0) != (ssize_t) rl0) {
        errn = errno;
        close(gfd);
        fprintf(stderr, "Error, failed to set direction for GPIO %d: %s\n",
            gpio->gpio_num, strerror(errn));
        fflush(stderr);
        return -1;
    }
    close(gfd);

    do {
        size_t triglen;
        const char * trig = "both\n";

        if (gpio->isout) {
            /*
             * For GPIO output, set the trigger mode to none
             * Because it will not be polled
             */
            trig = "none\n";
        }
        triglen = strlen(trig);

        /* for GPIO input, trigger at both edges */
        snprintf(gpath, sizeof(gpath), "/sys/class/gpio/gpio%d/edge", gpio->gpio_num);
        gfd = open(gpath, O_WRONLY | O_CLOEXEC | O_TRUNC);
        if (gfd == -1) {
            errn = errno;
            fprintf(stderr, "Error, failed to open(%s): %s\n", gpath, strerror(errn));
            fflush(stderr);
        } else {
            if (write(gfd, trig, triglen) != (ssize_t) triglen) {
                errn = errno;
                fprintf(stderr, "Error, failed to set trigger mode '%s' for GPIO %d: %s\n",
                    trig, gpio->gpio_num, strerror(errn));
                fflush(stderr);
            }
            close(gfd);
        }
    } while (0);

    snprintf(gpath, sizeof(gpath), "/sys/class/gpio/gpio%d/value", gpio->gpio_num);
    gfd = open(gpath, O_RDWR | O_CLOEXEC);
    if (gfd == -1) {
        errn = errno;
        fprintf(stderr, "Error, failed to open(%s): %s\n", gpath, strerror(errn));
        fflush(stderr);
        return -1;
    }
    gpio->gpio_fd = gfd;
    return 0;
}

static int lnxhal_gpio_get(struct lnxhal_gpio * gpio)
{
    off_t rval;
    ssize_t rl1;
    char gbuf[8];
    int errn, gpfd, gval;

    gpfd = gpio->gpio_fd;
    if (gpfd == -1)
        return -1;
    rval = lseek(gpfd, 0, SEEK_SET);
    if (rval != 0) {
        errn = errno;
        fprintf(stderr, "Error, failed to seek %d: %s\n",
            gpfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    rl1 = read(gpfd, gbuf, sizeof(gbuf));
    if (rl1 <= 0) {
        errn = errno;
        fprintf(stderr, "Error, failed to read(%d) has returned %d: %s\n",
            gpfd, (int) rl1, strerror(errn));
        fflush(stderr);
        return -1;
    }

    gval = -1;
    if (gbuf[0] == '0') {
        gval = 0;
        gpio->gpio_val = 0;
    } else {
        gval = 1;
        gpio->gpio_val = 1;
    }

    lseek(gpfd, 0, SEEK_SET);
    return gval;
}

int lnxhal_gpio_getval(struct lnxhal_gpio * gpios, int gnum)
{
    int idx, rval;
    struct lnxhal_gpio * gpio;

    rval = 0;
    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        if (lnxhal_gpio_get(gpio) < 0)
            rval--;
    }

    return rval;
}

int lnxhal_gpio_setval(struct lnxhal_gpio * gpio, int val)
{
    off_t rval;
    ssize_t rl1;
    int errn, gpfd;
    const char * gval;

    gpfd = gpio->gpio_fd;
    if (gpfd == -1)
        return -1;
    rval = lseek(gpfd, 0, SEEK_SET);
    if (rval != 0) {
        errn = errno;
        fprintf(stderr, "Error, failed to seek %d: %s\n",
            gpfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    gval = val ? "1\n" : "0\n";
    rl1 = write(gpfd, gval, 0x2);
    if (rl1 != 0x2) {
        errn = errno;
        fprintf(stderr, "Error, failed to read(%d) has returned %d: %s\n",
            gpfd, (int) rl1, strerror(errn));
        fflush(stderr);
        return -1;
    }

    gpio->gpio_val = val ? 1 : 0;
    lseek(gpfd, 0, SEEK_SET);
    return 0;
}

int lnxhal_gpio_poll(struct lnxhal_gpio * gpios, int gnum, int timeo)
{
    int rval;
    int idx, ret, errn;
    struct pollfd * pfd;
    struct pollfd * pfds;
    struct lnxhal_gpio * gpio;

    rval = 0;
    if (gnum <= 0 || gnum > LNXHAL_GPIO_MAXNUM) {
        fprintf(stderr, "Error, invalid number of GPIO: %d\n", gnum);
        fflush(stderr);
        return -1;
    }

    pfds = (struct pollfd *) calloc((size_t) gnum, sizeof(struct pollfd));
    if (pfds == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return -1;
    }
    for (idx = 0; idx < gnum; ++idx) {
        pfd = &pfds[idx];
        gpio = &gpios[idx];
        pfd->fd = gpio->gpio_fd;
        pfd->events = POLLPRI;
        pfd->revents = 0;
    }

    ret = poll(pfds, gnum, timeo);
    if (ret < 0) {
        errn = errno;
        free(pfds);
        if (errn == EINTR)
            return 0;
        fprintf(stderr, "Error, GPIO poll has failed: %s\n", strerror(errn));
        fflush(stderr);
        return -1;
    }

    if (ret == 0) {
        free(pfds);
        return 0; /* timed out */
    }
    for (idx = 0; idx < gnum; ++idx) {
        pfd = &pfds[idx];
        gpio = &gpios[idx];
        if (pfd->revents != 0) {
            gpio->changed = 1;
            rval++;
        }
    }

    free(pfds);
    return rval;
}

int lnxhal_gpio_free(struct lnxhal_gpio * gpios, int gnum)
{
    int idx;
    struct lnxhal_gpio * gpio;

    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        if (gpio->gpio_fd != -1) {
            close(gpio->gpio_fd);
            gpio->gpio_fd = -1;
        }
    }

    free(gpios);
    return 0;
}

int lnxhal_gpio_open(struct lnxhal_gpio * gpios, int gnum)
{
    int ret, idx, count;
    struct lnxhal_gpio * gpio;

    count = 0;
    for (idx = 0; idx < gnum; ++idx) {
        gpio = &gpios[idx];
        ret = lnxhal_gpio_reopen(gpio);
        if (ret < 0) {
            fprintf(stderr, "Error, failed to open GPIO %d!\n", gpio->gpio_num);
            fflush(stderr);
            continue;
        }
        count++;
    }
    return count;
}
