/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2007 by Michael Sevakis
 *
 * 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 "system.h"
#include "i2c-s3c2440.h"

static struct wakeup i2c_wake; /* Transfer completion signal */
static struct mutex i2c_mtx;   /* Mutual exclusion */
static unsigned char *buf_ptr; /* Next byte to transfer */
static int buf_count;          /* Number of bytes remaining to transfer */

static void i2c_stop(void)
{
    /* Generate STOP */
    IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_RXTX_ENB;

    /* No more interrupts, clear pending interrupt to continue */
    IICCON &= ~(I2C_TXRX_INTPND | I2C_TXRX_INTENB);
}

void i2c_write(int addr, const unsigned char *buf, int count)
{
    if (count <= 0)
        return;

    mutex_lock(&i2c_mtx);

    /* Turn on I2C clock */
    bitset32(&CLKCON, 1 << 16);

    /* Set mode to master transmitter and enable lines */
    IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_RXTX_ENB;

    /* Set buffer start and count */
    buf_ptr = (unsigned char *)buf;
    buf_count = count;

    /* Send slave address and then data */
    SRCPND = IIC_MASK;
    INTPND = IIC_MASK;

    IICCON |= I2C_TXRX_INTENB;

    /* Load slave address into shift register */
    IICDS = addr & 0xfe;

    /* Generate START */
    IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_START | I2C_RXTX_ENB;

    if (wakeup_wait(&i2c_wake, HZ) != OBJ_WAIT_SUCCEEDED)
    {
        /* Something went wrong - stop transmission */
        int oldlevel = disable_irq_save();
        i2c_stop();
        restore_irq(oldlevel);
    }

    /* Go back to slave receive mode and disable lines */
    IICSTAT = 0;

    /* Turn off I2C clock */
    bitclr32(&CLKCON, 1 << 16);

    mutex_unlock(&i2c_mtx);
}

void i2c_init(void)
{
    /* Init kernel objects */
    wakeup_init(&i2c_wake);
    mutex_init(&i2c_mtx);

    /* Clear pending source */
    SRCPND = IIC_MASK;
    INTPND = IIC_MASK;

    /* Enable i2c interrupt in controller */
    bitclr32(&INTMOD, IIC_MASK);
    bitclr32(&INTMSK, IIC_MASK);

    /* Turn on I2C clock */
    bitset32(&CLKCON, 1 << 16);

    /* Set GPE15 (IICSDA) and GPE14 (IICSCL) to IIC */
    GPECON = (GPECON & ~((3 << 30) | (3 << 28))) |
                ((2 << 30) | (2 << 28));

    /* Bus ACK, IICCLK: fPCLK / 16, Rx/Tx Int: Disable, Tx clock: IICCLK/8 */
    /* OF PCLK: 49.1568MHz / 16 / 8 = 384.0375 kHz */
    IICCON = (7 << 0);

    /* SDA line delayed 0 PCLKs */
    IICLC = (0 << 0);

    /* Turn off I2C clock */
    bitclr32(&CLKCON, 1 << 16);
}

void IIC(void)
{
    for (;;)
    {
        /* If ack was received from last byte and bytes are remaining */
        if (--buf_count >= 0 && (IICSTAT & I2C_ACK_L) == 0)
        {
            /* Write next byte to shift register */
            IICDS = *buf_ptr++;

            /* Clear pending interrupt to continue */
            IICCON &= ~I2C_TXRX_INTPND;
            break;
        }

        /* Finished */

        /* Generate STOP */
        i2c_stop();

        /* Signal thread */
        wakeup_signal(&i2c_wake);
        break;
    }

    /* Ack */
    SRCPND = IIC_MASK;
    INTPND = IIC_MASK;
}
