/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: i2c-imx31.c 17847 2008-06-28 18:10:04Z bagder $
 *
 * Copyright (C) 2008 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 <stdlib.h>
#include "config.h"
#include "system.h"
#include "kernel.h"
#include "avic-imx31.h"
#include "clkctl-imx31.h"
#include "i2c-imx31.h"

/* Forward interrupt handler declarations */
#if (I2C_MODULE_MASK & USE_I2C1_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C1_HANDLER(void);
#endif
#if (I2C_MODULE_MASK & USE_I2C2_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C2_HANDLER(void);
#endif
#if (I2C_MODULE_MASK & USE_I2C3_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C3_HANDLER(void);
#endif

static struct i2c_module_descriptor
{
    struct i2c_map *base;     /* Module base address */
    enum IMX31_CG_LIST cg;    /* Clock gating index */
    enum IMX31_INT_LIST ints; /* Module interrupt number */
    int enable;               /* Enable count */
    void (*handler)(void);    /* Module interrupt handler */
    struct mutex m;           /* Node mutual-exclusion */
    struct wakeup w;          /* I2C done signal */
    unsigned char *addr_data; /* Additional addressing data */
    int addr_count;           /* Addressing byte count */
    unsigned char *data;      /* TX/RX buffer (actual data) */
    int data_count;           /* TX/RX byte count */
    unsigned char addr;       /* Address + r/w bit */
} i2c_descs[I2C_NUM_I2C] =
{
#if (I2C_MODULE_MASK & USE_I2C1_MODULE)
    {
        .base    = (struct i2c_map *)I2C1_BASE_ADDR,
        .cg      = CG_I2C1,
        .ints    = I2C1,
        .handler = I2C1_HANDLER,
    },
#endif
#if (I2C_MODULE_MASK & USE_I2C2_MODULE)
    {
        .base    = (struct i2c_map *)I2C2_BASE_ADDR,
        .cg      = CG_I2C2,
        .ints    = I2C2,
        .handler = I2C2_HANDLER,
    },
#endif
#if (I2C_MODULE_MASK & USE_I2C3_MODULE)
    {
        .base    = (struct i2c_map *)I2C3_BASE_ADDR,
        .cg      = CG_I2C3,
        .ints    = I2C3,
        .handler = I2C3_HANDLER,
    },
#endif
};

static void i2c_interrupt(enum i2c_module_number i2c)
{
    struct i2c_module_descriptor *const desc = &i2c_descs[i2c];
    struct i2c_map * const base = desc->base;
    uint16_t i2sr = base->i2sr;

    base->i2sr = i2sr & ~I2C_I2SR_IIF; /* Clear IIF */

    if (desc->addr_count >= 0)
    {
        /* ADDR cycle - either done or more to send */
        if ((i2sr & I2C_I2SR_RXAK) != 0)
        {
            goto i2c_stop; /* problem */
        }

        if (--desc->addr_count < 0)
        {
            /* Switching to data cycle */
            if (desc->addr & 0x1)
            {
                base->i2cr &= ~I2C_I2CR_MTX; /* Switch to RX mode */
                base->i2dr;                  /* Dummy read */
                return;
            }
            /* else remaining data is TX - handle below */
            goto i2c_transmit;
        }
        else
        {
            base->i2dr = *desc->addr_data++; /* Send next addressing byte */
            return;
        }
    }

    if (base->i2cr & I2C_I2CR_MTX)
    {
i2c_transmit:
        /* Transmitting data */
        if ((i2sr & I2C_I2SR_RXAK) == 0 && desc->data_count > 0)
        {
            /* More bytes to send, got ACK from previous byte */
            base->i2dr = *desc->data++;
            desc->data_count--;
            return;
        }
        /* else done or no ACK received */
    }
    else
    {
        /* Receiving data */
        if (--desc->data_count > 0)
        {
            if (desc->data_count == 1)
            {
                /* 2nd to Last byte - NACK */
                base->i2cr |= I2C_I2CR_TXAK;
            }

            *desc->data++ = base->i2dr; /* Read data from I2DR and store */
            return;
        }
        else
        {
            /* Generate STOP signal before reading data */
            base->i2cr &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
            *desc->data++ = base->i2dr; /* Read data from I2DR and store */
            goto i2c_done;
        }
    }

i2c_stop:
    /* Generate STOP signal */
    base->i2cr &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
i2c_done:
    /* Signal thread we're done */
    wakeup_signal(&desc->w);
}

#if (I2C_MODULE_MASK & USE_I2C1_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C1_HANDLER(void)
{
    i2c_interrupt(I2C1_NUM);
}
#endif
#if (I2C_MODULE_MASK & USE_I2C2_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C2_HANDLER(void)
{
    i2c_interrupt(I2C2_NUM);
}
#endif
#if (I2C_MODULE_MASK & USE_I2C3_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C3_HANDLER(void)
{
    i2c_interrupt(I2C3_NUM);
}
#endif

static int i2c_transfer(struct i2c_node * const node,
                        struct i2c_module_descriptor *const desc)
{
    struct i2c_map * const base = desc->base;
    int count = desc->data_count;
    uint16_t i2cr;

    /* Set speed */
    base->ifdr = node->ifdr;

    /* Enable module */
    base->i2cr = I2C_I2CR_IEN;

    /* Enable Interrupt, Master */
    i2cr = I2C_I2CR_IEN | I2C_I2CR_IIEN | I2C_I2CR_MTX;

    if ((desc->addr & 0x1) && desc->data_count < 2)
    {
        /* Receiving less than two bytes - disable ACK generation */
        i2cr |= I2C_I2CR_TXAK;
    }

    /* Set config */
    base->i2cr = i2cr;

    /* Generate START */
    base->i2cr |= I2C_I2CR_MSTA;

    /* Address slave (first byte sent) and begin session. */
    base->i2dr = desc->addr;

    /* Wait for transfer to complete */
    count = (wakeup_wait(&desc->w, HZ) != OBJ_WAIT_SUCCEEDED) ?
                -1 : (count - desc->data_count);
    
    /* Disable module - generate STOP if timeout */
    base->i2cr = 0;

    return count;
}

int i2c_read(struct i2c_node *node, int reg,
             unsigned char *data, int data_count)
{
    struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
    unsigned char ad[1];

    mutex_lock(&desc->m);

    desc->addr = (node->addr & 0xfe) | 0x1;  /* Slave address/rd */

    if (reg >= 0)
    {
        /* Sub-address */
        desc->addr_count = 1;
        desc->addr_data = ad;
        ad[0] = reg;
    }
    /* else raw read from slave */

    desc->data = data;
    desc->data_count = data_count;

    data_count = i2c_transfer(node, desc);

    desc->addr_count = 0; /* To eliminate zeroing elsewhere */

    mutex_unlock(&desc->m);

    return data_count;
}

int i2c_write(struct i2c_node *node, const unsigned char *data, int data_count)
{
    struct i2c_module_descriptor *const desc = &i2c_descs[node->num];

    mutex_lock(&desc->m);

    desc->addr = node->addr & 0xfe; /* Slave address/wr */
    desc->data = (unsigned char *)data;
    desc->data_count = data_count;

    data_count = i2c_transfer(node, desc);

    mutex_unlock(&desc->m);

    return data_count;
}

void i2c_init(void)
{
    int i;

    /* Do one-time inits for each module that will be used - leave
     * module disabled and unclocked until something wants it */
    for (i = 0; i < I2C_NUM_I2C; i++)
    {
        struct i2c_module_descriptor *const desc = &i2c_descs[i];
        imx31_clkctl_module_clock_gating(desc->cg, CGM_ON_ALL);
        mutex_init(&desc->m);
        wakeup_init(&desc->w);
        desc->base->i2cr = 0;
        imx31_clkctl_module_clock_gating(desc->cg, CGM_OFF);
    }

#if 0
    /* Pad config set up by OF bootloader doesn't agree with manual but
     * TX works at the moment - probably would't do this here either */
    uint32_t reg = SW_PAD_CTL_CSI_PIXCLK_I2C_CLK_I2C_DAT;
    reg &= ~0xfffff;
    reg |= (1 << 19) | (3 << 17) | (1 << 15) | (1 << 14) |
           (1 << 13) | (0 << 11);

    reg |= (1 << 9) | (3 << 7) | (1 << 5) | (1 << 4) |
           (1 << 3) | (0 << 1);

    SW_PAD_CTL_CSI_PIXCLK_I2C_CLK_I2C_DAT = reg;
#endif
}

void i2c_enable_node(struct i2c_node *node, bool enable)
{
    struct i2c_module_descriptor *const desc = &i2c_descs[node->num];

    mutex_lock(&desc->m);

    if (enable)
    {
        if (++desc->enable == 1)
        {
            /* First enable */
            imx31_clkctl_module_clock_gating(desc->cg, CGM_ON_ALL);
            avic_enable_int(desc->ints, IRQ, 7, desc->handler);
        }
    }
    else
    {
        if (desc->enable > 0 && --desc->enable == 0)
        {
            /* Last enable */
            avic_disable_int(desc->ints);
            imx31_clkctl_module_clock_gating(desc->cg, CGM_OFF);
        }
    }

    mutex_unlock(&desc->m);
}

void i2c_lock_node(struct i2c_node *node)
{
    struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
    mutex_lock(&desc->m);
}

void i2c_unlock_node(struct i2c_node *node)
{
    struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
    mutex_unlock(&desc->m);
}
