/*
    zgpio_sysgpio.c - GPIO chip /sys/class/gpio
    Copyright (C) 2018 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/


#include <libziaint.h>
#include <zgpio.h>
#include <zfile.h>
#include <zstr.h>
#include <zselect.h>

#include <fcntl.h>
#include <glib.h>   
#include <stdlib.h>
#include <string.h>
#ifdef Z_HAVE_UNISTD_H
#include <unistd.h>
#endif

#define SYSFS_EXPORT "/sys/class/gpio/export"
#define SYSFS_UNEXPORT "/sys/class/gpio/unexport"
#define SYSFS_DIRECTION "/sys/class/gpio/gpio%d/direction"
#define SYSFS_VALUE "/sys/class/gpio/gpio%d/value"
#define SYSFS_EDGE "/sys/class/gpio/gpio%d/edge"


static void zgpio_sysgpio_freechip(struct zgpiochip *chip){
    
}

static int zgpio_sysgpio_open(struct zgpio *gpio){
    char file[64];

    int ret = zfile_printfile(SYSFS_EXPORT, "%d", gpio->nr);
    if (ret < 0) return ret;

    sprintf(file, SYSFS_VALUE, gpio->nr);
    gpio->value_fd = open(file, O_RDWR);
    if (gpio->value_fd < 0) return gpio->value_fd;
	
    return 0;
}

static struct zgpio *zgpio_sysgpio_init_nr(struct zgpiochip *chip, int nr){
    struct zgpio *gpio = g_new0(struct zgpio, 1);
    gpio->chip = chip;
    gpio->nr = nr;
    gpio->name = g_strdup_printf("P%c%d", 'A' + (nr / 32), nr % 32);

    int ret = zgpio_sysgpio_open(gpio);
    if (ret < 0){
		zgpio_free(gpio);
		return NULL;
    }
    return gpio;
}

static struct zgpio *zgpio_sysgpio_init_name(struct zgpiochip *chip, const char *name){
	struct zgpio *gpio;

    if (name == NULL || strlen(name) < 3) return NULL;
    if (z_char_uc(name[0]) != 'P') return NULL;
    
    gpio = g_new0(struct zgpio, 1);
    gpio->chip = chip;
    gpio->name = g_strdup(name);
    gpio->nr = atoi(name + 2);
    gpio->nr += (z_char_uc(gpio->name[1]) - 'A') * 32;

    int ret = zgpio_sysgpio_open(gpio);
    if (ret < 0){
		zgpio_free(gpio);
		return NULL;
    }
    return gpio;
}

static void zgpio_sysgpio_free(struct zgpio *gpio){
    char file[64];
    if (!gpio) return;

    if (gpio->edge != ZGPIO_EDGE_NONE){
        sprintf(file, SYSFS_EDGE, gpio->nr);
        zfile_printfile(file, "none");
    }

    if (gpio->value_fd >= 0) {
        if (gpio->zsel != NULL) zselect_set(gpio->zsel, gpio->value_fd, NULL, NULL, NULL, NULL);
        close(gpio->value_fd);
    }

    if (gpio->unexport_after) {
        zfile_printfile(SYSFS_UNEXPORT, "%d", gpio->nr);
    }
}

static int zgpio_sysgpio_dir_input(struct zgpio *gpio){
    char file[64];

    sprintf(file, SYSFS_DIRECTION, gpio->nr);
    return zfile_printfile(file, "in");
}


static int zgpio_sysgpio_dir_output(struct zgpio *gpio){
    char file[64];

    sprintf(file, SYSFS_DIRECTION, gpio->nr);
    return zfile_printfile(file, "out");
}

static int zgpio_sysgpio_write(struct zgpio *gpio, int value){
    int ret = lseek(gpio->value_fd, 0, SEEK_SET);
    if (ret < 0) return ret;

    ret = write(gpio->value_fd, value ? "1" : "0", 1);
    return ret;
}

static int zgpio_sysgpio_read(struct zgpio *gpio){
    char buf[2];

    int ret = lseek(gpio->value_fd, 0, SEEK_SET);
    if (ret < 0) return ret;

    ret = read(gpio->value_fd, buf, 1);
    if (ret < 0) return ret;

    return buf[0] != '0';

}

static void zgpio_sysgpio_handler(void *xxx)
{
    struct zgpio *gpio = (struct zgpio*)xxx;
    int value = zgpio_sysgpio_read(gpio);
    if (gpio->handler){
        gpio->handler(gpio, value, gpio->data);
    }
}

int zgpio_sysgpio_set_handler(struct zgpio *gpio, struct zselect *zsel, enum zgpio_edge edge, 
        void (*handler)(struct zgpio *gpio, int value, void *data), 
        void *data){

    char file[64];
    int ret;

    gpio->edge = edge;
    gpio->handler = handler;
    gpio->data = data;

    sprintf(file, SYSFS_DIRECTION, gpio->nr);
    ret = zfile_printfile(file, "in");
    if (ret < 0) return -1;

    sprintf(file, SYSFS_EDGE, gpio->nr);
    switch (edge){
        case ZGPIO_EDGE_NONE:
            ret = zfile_printfile(file, "none");
            break;
        case ZGPIO_EDGE_RISING:
            ret = zfile_printfile(file, "rising");
            break;
        case ZGPIO_EDGE_FALLING:
            ret = zfile_printfile(file, "falling");
            break;
        case ZGPIO_EDGE_BOTH:
            ret = zfile_printfile(file, "both");
            break;
        default:
            return -1;
    }

    if (ret < 0) return ret;
    
    zselect_set(zsel, gpio->value_fd, NULL, NULL, zgpio_sysgpio_handler, gpio);
    return 0;
}






struct zgpiochip *zgpiochip_init_sysgpio(void){
    struct zgpiochip *chip = g_new0(struct zgpiochip, 1);

    chip->freechip = zgpio_sysgpio_freechip;
    chip->init_nr = zgpio_sysgpio_init_nr;
    chip->init_name = zgpio_sysgpio_init_name;
    chip->free = zgpio_sysgpio_free;
    chip->dir_input = zgpio_sysgpio_dir_input;
    chip->dir_output = zgpio_sysgpio_dir_output;
    chip->write = zgpio_sysgpio_write;
    chip->read = zgpio_sysgpio_read;
    chip->set_handler = zgpio_sysgpio_set_handler;

    return chip;
}
