/*
    zgpio_mcp23017.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 <zdebug.h>
#include <zfile.h>
#include <zbus.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 IODIR   0x00
#define IPOL    0x01
#define GPINTEN 0x02
#define DEFVALA 0x03
#define INTCON  0x04
#define IOCON   0x05
#define GPPU    0x06
#define INTF    0x07
#define INTCAP  0x08
#define GPIO    0x09
#define OLAT    0x0a

static int zgpiochip_mcp23017_write_reg(struct zgpiochip *chip, unsigned char baseaddr, unsigned int ch, unsigned char value){
    unsigned char addr = baseaddr * 2 + ch;
    return zbus_write_reg(chip->dev, addr, value);
}

static int zgpiochip_mcp23017_read_reg(struct zgpiochip *chip, unsigned char baseaddr, unsigned int ch){
    unsigned int addr = baseaddr * 2 + ch;
    return zbus_read_reg(chip->dev, addr); 
}

static int zgpio_mcp23017_write_reg(struct zgpio *gpio, unsigned char baseaddr, unsigned char value){
    return zgpiochip_mcp23017_write_reg(gpio->chip, baseaddr, gpio->nr / 8, value);
}

static int zgpio_mcp23017_read_reg(struct zgpio *gpio, unsigned char baseaddr){
    return zgpiochip_mcp23017_read_reg(gpio->chip, baseaddr, gpio->nr / 8); 
}



static void zgpio_mcp23017_freechip(struct zgpiochip *chip){

    int n;
    for (n = 0; n < chip->gpios->len; n++){ 
        struct zgpio *gpio = (struct zgpio *)g_ptr_array_index(chip->gpios, n);
        zgpio_free(gpio);
    }
    g_ptr_array_free(chip->gpios, TRUE);
}

static int zgpio_mcp23017_open(struct zgpio *gpio){
    int ret = 0;
    gpio->mask = 1 << (gpio->nr % 8);

    g_ptr_array_add(gpio->chip->gpios, gpio);

    ZRET(zgpio_mcp23017_read_reg(gpio, OLAT));
    gpio->chip->olat[gpio->nr / 8] = (unsigned char)ret;
	return 0;
}

static struct zgpio *zgpio_mcp23017_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("GP%c%d", 'A' + (nr / 8), nr % 8);

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

static struct zgpio *zgpio_mcp23017_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]) != 'G') return NULL;
    if (z_char_uc(name[1]) != 'P') return NULL;
    if (z_char_uc(name[2]) != 'A' && z_char_uc(name[2]) != 'B') return NULL;
    
    gpio = g_new0(struct zgpio, 1);
    gpio->chip = chip;
    gpio->name = g_strdup(name);
    gpio->nr = atoi(name + 3);
    gpio->nr += (z_char_uc(gpio->name[2]) - 'A') * 8;

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

static void zgpio_mcp23017_free(struct zgpio *gpio){
    if (!gpio) return;

    g_ptr_array_remove(gpio->chip->gpios, gpio);

}

static int zgpio_mcp23017_dir_input(struct zgpio *gpio){
    int reg = zgpio_mcp23017_read_reg(gpio, IODIR);
    if (reg < 0) return reg;
    //printf("IODIR=0x%x ", reg);

    reg |= gpio->mask;
    //printf("-> 0x%x\n", reg);
    reg = zgpio_mcp23017_write_reg(gpio, IODIR, reg);
    return reg;
}


static int zgpio_mcp23017_dir_output(struct zgpio *gpio){
    int reg = zgpio_mcp23017_read_reg(gpio, IODIR);
    if (reg < 0) return reg;
    //printf("IODIR=0x%x ", reg);

    reg &= ~gpio->mask;
    //printf("-> 0x%x\n", reg);
    reg = zgpio_mcp23017_write_reg(gpio, IODIR, reg);
    return reg;
}

static int zgpio_mcp23017_write(struct zgpio *gpio, int value){
    int ret = 0;
    unsigned char old = gpio->chip->olat[gpio->nr/8];

    if (value)
        gpio->chip->olat[gpio->nr/8] |= gpio->mask;
    else
        gpio->chip->olat[gpio->nr/8] &= ~gpio->mask;

    if (gpio->chip->olat[gpio->nr/8] == old) return 0;
    ZRET(zgpio_mcp23017_write_reg(gpio, OLAT, gpio->chip->olat[gpio->nr/8]));
    return ret;
}

static int zgpio_mcp23017_read(struct zgpio *gpio){
    int reg = zgpiochip_mcp23017_read_reg(gpio->chip, GPIO, gpio->nr / 8);
    if (reg < 0) return reg;

    gpio->chip->gpio[gpio->nr / 8] = (unsigned char)reg;

    if ((reg & gpio->mask) != 0)
        return 1;
    else
        return 0;
}

static void zgpio_mcp23017_inta(struct zgpio *inta, int value, void *data)
{
    struct zgpiochip *chip = (struct zgpiochip*)data;
    dbg("\n--------------\nzgpio_mcp23017_inta value=0x%02x\n", value);
    
    int ch;
    for (ch = 0; ch < 2; ch++){
        int val = zgpiochip_mcp23017_read_reg(chip, INTF, ch);
        val = zgpiochip_mcp23017_read_reg(chip, INTCAP, ch);
        
        //int val = zgpiochip_mcp23017_read_reg(chip, GPIO, ch);
        dbg("GPIO[%d] = 0x%02x -> 0x%02x\n", ch, chip->gpio[ch], (unsigned char)val);
        int n;
        for (n = 0; n < chip->gpios->len; n++){ 
            struct zgpio *gpio = (struct zgpio *)g_ptr_array_index(chip->gpios, n);
            if (gpio->nr / 8 != ch) continue;

            //dbg("testing nr=%d\n", gpio->nr);

            if ((val & gpio->mask) == (chip->gpio[ch] & gpio->mask)) continue;
            dbg("firing nr=%d\n", gpio->nr);

            if (gpio->handler){
                gpio->handler(gpio, (val & gpio->mask) ? 1 : 0, gpio->data);
            }
        }
        chip->gpio[ch] = (unsigned char)val;
    }
}

int zgpio_mcp23017_set_handler(struct zgpio *gpio, struct zselect *zsel, enum zgpio_edge edge, 
        void (*handler)(struct zgpio *gpio, int value, void *data), 
        void *data){
//    int ret;
    if (!gpio) return -1;

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

    int reg = zgpio_mcp23017_read_reg(gpio, GPINTEN);
    if (reg < 0) return reg;
    
    if (edge != ZGPIO_EDGE_NONE){
        reg |= gpio->mask;
    }else{
        reg &= ~gpio->mask;
    }
    int ret = zgpio_mcp23017_write_reg(gpio, GPINTEN, reg);
    if (ret < 0) return ret;
    
    return 0;
}






struct zgpiochip *zgpiochip_init_mcp23017(struct zbusdev *dev, struct zgpio *inta, struct zselect *zsel, int mirror){
    struct zgpiochip *chip = g_new0(struct zgpiochip, 1);
    
    chip->dev = dev;
    chip->inta = inta;

    chip->freechip = zgpio_mcp23017_freechip;
    chip->init_nr = zgpio_mcp23017_init_nr;
    chip->init_name = zgpio_mcp23017_init_name;
    chip->free = zgpio_mcp23017_free;
    chip->dir_input = zgpio_mcp23017_dir_input;
    chip->dir_output = zgpio_mcp23017_dir_output;
    chip->write = zgpio_mcp23017_write;
    chip->read = zgpio_mcp23017_read;
    chip->set_handler = zgpio_mcp23017_set_handler;
    chip->mirror = mirror;
    
    int ch;
    for (ch = 0; ch < 2; ch++){
        int ret = zgpiochip_mcp23017_read_reg(chip, GPIO, ch);
        if (ret < 0){
            g_free(chip);
            return NULL;
        }
        
        ret = zgpiochip_mcp23017_read_reg(chip, INTF, ch);
    }

    chip->gpios = g_ptr_array_new();

    if (inta){
        zgpio_set_handler(inta, zsel, ZGPIO_EDGE_RISING, zgpio_mcp23017_inta, chip);
    }

    return chip;
}
