/*
    zspigpio.c - SPI over spigpio
    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 <zbus.h>
#include <zdebug.h>
#include <zgpio.h>
#include <zspigpio.h>

#include <unistd.h>


static int zspigpio_shout(struct zbusdev *dev, void *buf, size_t len){
    unsigned char acc;
    int i, j, ret = 0;
    unsigned char *data = (unsigned char *)buf;
    
    if (dev->miso == dev->mosi){
        ZRET(zgpio_dir_output(dev->mosi));
    }

    for (i = 0; i < len; i++){
        acc = *data;
        for (j = 0; j < 8; j++){
            ZRET(zgpio_write(dev->mosi, (acc & 0x80) != 0 ? 1 : 0));
            ZRET(zgpio_write(dev->sclk, 1));
            if (dev->sleep_us > 0) usleep(dev->sleep_us);
            ZRET(zgpio_write(dev->sclk, 0));
            if (dev->sleep_us > 0) usleep(dev->sleep_us);
            acc <<= 1;
        } 
        data++;
        ret++;
    }
    return ret;
}


static int zspigpio_shin(struct zbusdev *dev, void *buf, size_t len){
    unsigned char acc;
    int i, j, ret = 0;
    unsigned char *data = (unsigned char *)buf;
    
    if (dev->miso == dev->mosi){
        ZRET(zgpio_dir_input(dev->miso));
    }

    for (i = 0; i < len; i++){
        acc = 0;
        for (j = 0; j < 8; j++){
            ZRET(zgpio_write(dev->sclk, 1));
            if (dev->sleep_us > 0) usleep(dev->sleep_us);

            ZRET(zgpio_read(dev->miso));
            acc <<= 1;
            if (ret) acc |= 1;

            ZRET(zgpio_write(dev->sclk, 0));
            if (dev->sleep_us > 0) usleep(dev->sleep_us);
        } 
        *data = acc;
        data++;
        ret++;
    }
    
    return ret;
}


int zspigpio_write(struct zbusdev *dev, void *buf, size_t len){
    int ret;

    ZRETX(zgpio_write(dev->sclk, 0));
    ZRETX(zgpio_write(dev->ss, dev->sspol/*0*/));
    ZRETX(zspigpio_shout(dev, buf, len));
x:;
    zgpio_write(dev->ss, !dev->sspol/*1*/);
    return ret;
}


int zspigpio_read(struct zbusdev *dev, void *buf, size_t len){
    int ret;

    ZRETX(zgpio_write(dev->sclk, 0));
    ZRETX(zgpio_write(dev->ss, dev->sspol/*0*/));
    ZRETX(zspigpio_shin(dev, buf, len));
x:;    
    zgpio_write(dev->ss, !dev->sspol/*1*/);
    return ret;
}


int zspigpio_read_regs(struct zbusdev *dev, unsigned char addr, void *buf, size_t len){
    int ret;
    unsigned char b = addr | 0x80;

    ZRETX(zgpio_write(dev->sclk, 0));
    ZRETX(zgpio_write(dev->ss, dev->sspol/*0*/));

    ZRETX(zspigpio_shout(dev, &b, 1));
    ZRETX(zspigpio_shin(dev, buf, len));
x:;    
    zgpio_write(dev->ss, !dev->sspol/*1*/);
    return ret;
}


int zspigpio_free(struct zbusdev *dev){
    if (dev->fd >= 0){
        close(dev->fd);
        dev->fd = -1;
    }
    return 0;
}


struct zbusdev *zspigpio_init(struct zgpio *sclk, struct zgpio *mosi, struct zgpio *miso, struct zgpio *ss, int sleep_us, int sspol){

    if (!sclk) {
        error("zspigpio_init: sclk is NULL\n");  
        return NULL;
    }
    if (!mosi) {
        error("zspigpio_init: mosi is NULL\n");  
        return NULL;
    }
    if (!miso) {
        error("zspigpio_init: miso is NULL\n");  
        return NULL;
    }
    if (!ss) {
        error("zspigpio_init: ss is NULL\n");  
        return NULL;
    }

    int ret;
    ret = zgpio_dir_output(sclk);
    if (ret < 0) {
        error("zspigpio_init: Can't set SCLK to output\n");
        return NULL;
    }

    ret = zgpio_dir_output(mosi);
    if (ret < 0) {
        error("zspigpio_init: Can't set MOSI to output\n");
        return NULL;
    }

    if (miso != mosi){
        ret = zgpio_dir_input(miso);
        if (ret < 0) {
            error("zspigpio_init: Can't set MISO to input\n");
            return NULL;
        }
    }

    ret = zgpio_dir_output(ss);
    if (ret < 0) {
        error("zspigpio_init: Can't set SS to output\n");
        return NULL;
    }


    struct zbusdev *dev = (struct zbusdev *)g_new0(struct zbusdev, 1);
    dev->sclk = sclk;
    dev->mosi = mosi;
    dev->miso = miso;
    dev->ss = ss;
    dev->sleep_us = sleep_us;
    dev->sspol = sspol;
    
    dev->free = zspigpio_free;
    dev->write = zspigpio_write;
    dev->read = zspigpio_read;
    dev->read_regs = zspigpio_read_regs;

    ZRETX(zgpio_write(dev->sclk, 0));
    // th(cs_xl, cs_mag) >= 8ns
    ZRETX(zgpio_write(dev->ss, !dev->sspol/*1*/));
    return dev;

x:;
    zbus_free(dev);
    return NULL;  
}

