/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: generic_i2c.c 17847 2008-06-28 18:10:04Z bagder $
 *
 * Copyright (C) 2006 by Linus Nielsen Feltzing
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "config.h"
#include "cpu.h"
#include <stdbool.h>
#include <stdlib.h>
#include "debug.h"
#include "logf.h"
#include "generic_i2c.h"

int i2c_num_ifs = 0;
struct i2c_interface *i2c_if[5];

static void i2c_start(struct i2c_interface *iface)
{
    iface->sda_hi();
    iface->scl_hi();
    iface->sda_lo();
    iface->delay_hd_sta();
    iface->scl_lo();
}

static void i2c_stop(struct i2c_interface *iface)
{
    iface->sda_lo();
    iface->scl_hi();
    iface->delay_su_sto();
    iface->sda_hi();
}

static void i2c_ack(struct i2c_interface *iface, bool ack)
{
    iface->scl_lo();
    if ( ack )
        iface->sda_lo();
    else
        iface->sda_hi();

    iface->delay_su_dat();
    iface->scl_hi();
    iface->delay_thigh();
    iface->scl_lo();
}

static int i2c_getack(struct i2c_interface *iface)
{
    int ret = 1;

    iface->sda_input();
    iface->delay_su_dat();
    iface->scl_hi();
    
    if (iface->sda())
        ret = 0; /* ack failed */
    
    iface->delay_thigh();
    iface->scl_lo();
    iface->sda_hi();
    iface->sda_output();
    iface->delay_hd_dat();
    return ret;
}

static unsigned char i2c_inb(struct i2c_interface *iface, bool ack)
{
    int i;
    unsigned char byte = 0;
    
    /* clock in each bit, MSB first */
    for ( i=0x80; i; i>>=1 ) {
        iface->sda_input();
        iface->scl_hi();
        iface->delay_thigh();
        if (iface->sda())
            byte |= i;
        iface->scl_lo();
        iface->delay_hd_dat();
        iface->sda_output();
    }
    
    i2c_ack(iface, ack);
    
    return byte;
}

static void i2c_outb(struct i2c_interface *iface, unsigned char byte)
{
   int i;

   /* clock out each bit, MSB first */
   for (i=0x80; i; i>>=1) {
       if (i & byte)
           iface->sda_hi();
       else
           iface->sda_lo();
       iface->delay_su_dat();
       iface->scl_hi();
       iface->delay_thigh();
       iface->scl_lo();
       iface->delay_hd_dat();
   }

   iface->sda_hi();
}

static struct i2c_interface *find_if(int address)
{
    int i;

    for(i = 0;i < i2c_num_ifs;i++) {
        if(i2c_if[i]->address == address)
            return i2c_if[i];
    }
    return NULL;
}

int i2c_write_data(int bus_address, int address,
                   const unsigned char* buf, int count)
{
    int i;
    int ret = 0;
    struct i2c_interface *iface = find_if(bus_address);
    if(!iface)
        return -1;
    
    i2c_start(iface);
    i2c_outb(iface, iface->address & 0xfe);
    if (i2c_getack(iface)) {
        i2c_outb(iface, address);
        if (i2c_getack(iface)) {
            for(i = 0;i < count;i++) {
                i2c_outb(iface, buf[i]);
                if (!i2c_getack(iface)) {
                    ret = -3;
                    break;
                }
            }
        } else {
            ret = -2;
        }
    } else {
        logf("i2c_write_data() - no ack\n");
        ret = -1;
    }
    
    i2c_stop(iface);
    return ret;
}

int i2c_read_data(int bus_address, int address,
                  unsigned char* buf, int count)
{
    int i;
    int ret = 0;
    struct i2c_interface *iface = find_if(bus_address);
    if(!iface)
        return -1;
    
    i2c_start(iface);
    i2c_outb(iface, iface->address & 0xfe);
    if (i2c_getack(iface)) {
        i2c_outb(iface, address);
        if (i2c_getack(iface)) {
            i2c_start(iface);
            i2c_outb(iface, iface->address | 1);
            if (i2c_getack(iface)) {
                for(i = 0;i < count-1;i++)
                    buf[i] = i2c_inb(iface, true);
                
                buf[i] = i2c_inb(iface, false);
            } else {
                ret = -3;
            }
        } else {
            ret = -2;
        }
    } else {
        ret = -1;
    }
    
    i2c_stop(iface);
    return ret;
}

void i2c_add_node(struct i2c_interface *iface)
{
    i2c_if[i2c_num_ifs++] = iface;
}
