/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2021 Aidan MacDonald
 *
 * 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 "i2c-async.h"
#include "config.h"
#include "system.h"
#include "kernel.h"

/* To decide on the queue size, typically you should use the formula:
 *
 *   queue size = max { D_1, ..., D_n } * max { T_1, ..., T_m }
 *
 * where
 *
 *     n = number of busses
 *     m = number of devices across all busses
 *   D_i = number of devices on bus i
 *   T_j = number of queued transactions needed for device j
 *
 * The idea is to ensure nobody waits for a queue slot, but keep the queue
 * size to a minimum so that queue management features don't take too long.
 * (They have to iterate over the entire queue with IRQs disabled, so...)
 *
 * If you don't use the asychronous features then waiting for a queue slot
 * is a moot point since you'll be blocking until completion anyway; in that
 * case feel free to use single-entry queues.
 *
 * Note that each bus gets the same sized queue. If this isn't good for
 * your target for whatever reason, it might be worth implementing per-bus
 * queue sizes here.
 */
#if !defined(I2C_ASYNC_BUS_COUNT) || !defined(I2C_ASYNC_QUEUE_SIZE)
# error "Need to add #defines for i2c-async frontend"
#endif

#if I2C_ASYNC_QUEUE_SIZE < 1
# error "i2c-async queue size must be >= 1"
#endif

/* Singly-linked list for queuing up transactions */
typedef struct i2c_async_txn {
    struct i2c_async_txn* next;
    i2c_descriptor* desc;
    int cookie;
} i2c_async_txn;

typedef struct i2c_async_bus {
    /* Head/tail of transaction queue. The head always points to the
     * currently running transaction, or NULL if the bus is idle.
     */
    i2c_async_txn* head;
    i2c_async_txn* tail;

    /* Head of a free list used to allocate nodes. */
    i2c_async_txn* free;

    /* Next unallocated cookie value */
    int nextcookie;

    /* Semaphore for reserving a node in the free list. Nodes can only be
     * allocated after a successful semaphore_wait(), and after being freed
     * semaphore_release() must be called.
     */
    struct semaphore sema;

#ifdef HAVE_CORELOCK_OBJECT
    /* Corelock is required for multi-core CPUs */
    struct corelock cl;
#endif
} i2c_async_bus;

static i2c_async_txn i2c_async_txns[I2C_ASYNC_BUS_COUNT][I2C_ASYNC_QUEUE_SIZE];
static i2c_async_bus i2c_async_busses[I2C_ASYNC_BUS_COUNT];

static i2c_async_txn* i2c_async_popfree(i2c_async_txn** head)
{
    i2c_async_txn* node = *head;
    *head = node->next;
    return node;
}

static void i2c_async_pushfree(i2c_async_txn** head, i2c_async_txn* node)
{
    node->next = *head;
    *head = node;
}

static i2c_async_txn* i2c_async_pool_init(i2c_async_txn* array, int count)
{
    /* Populate forward pointers */
    for(int i = 0; i < count - 1; ++i)
        array[i].next = &array[i+1];

    /* Last pointer is NULL */
    array[count - 1].next = NULL;

    /* Return head of pool */
    return &array[0];
}

static void i2c_async_bus_init(int busnr)
{
    const int q_size = I2C_ASYNC_QUEUE_SIZE;

    i2c_async_bus* bus = &i2c_async_busses[busnr];
    bus->head = bus->tail = NULL;
    bus->free = i2c_async_pool_init(i2c_async_txns[busnr], q_size);
    bus->nextcookie = 1;
    semaphore_init(&bus->sema, q_size, q_size);
    corelock_init(&bus->cl);
}

/* Add a node to the end of the transaction queue */
static void i2c_async_bus_enqueue(i2c_async_bus* bus, i2c_async_txn* node)
{
    node->next = NULL;
    if(bus->head == NULL)
        bus->head = bus->tail = node;
    else {
        bus->tail->next = node;
        bus->tail = node;
    }
}

/* Helper function called to run descriptor completion tasks */
static void i2c_async_bus_complete_desc(i2c_async_bus* bus, int status)
{
    i2c_descriptor* d = bus->head->desc;
    if(d->callback)
        d->callback(status, d);

    bus->head->desc = d->next;
}

void __i2c_async_complete_callback(int busnr, int status)
{
    i2c_async_bus* bus = &i2c_async_busses[busnr];
    corelock_lock(&bus->cl);

    i2c_async_bus_complete_desc(bus, status);
    if(status != I2C_STATUS_OK) {
        /* Skip remainder of transaction after an error */
        while(bus->head->desc)
            i2c_async_bus_complete_desc(bus, I2C_STATUS_SKIPPED);
    }

    /* Dequeue next transaction if we finished the current one */
    if(!bus->head->desc) {
        i2c_async_txn* node = bus->head;
        bus->head = node->next;
        i2c_async_pushfree(&bus->free, node);
        semaphore_release(&bus->sema);
    }

    if(bus->head) {
        /* Submit the next descriptor */
        __i2c_async_submit(busnr, bus->head->desc);
    } else {
        /* Fixup tail after last transaction */
        bus->tail = NULL;
    }

    corelock_unlock(&bus->cl);
}

void __i2c_async_init(void)
{
    for(int i = 0; i < I2C_ASYNC_BUS_COUNT; ++i)
        i2c_async_bus_init(i);
}

int i2c_async_queue(int busnr, int timeout, int q_mode,
                    int cookie, i2c_descriptor* desc)
{
    i2c_async_txn* node;
    i2c_async_bus* bus = &i2c_async_busses[busnr];
    int rc = I2C_RC_OK;

    int irq = disable_irq_save();
    corelock_lock(&bus->cl);

    /* Scan the queue unless q_mode is a simple ADD */
    if(q_mode != I2C_Q_ADD) {
        for(node = bus->head; node != NULL; node = node->next) {
            if(node->cookie == cookie) {
                if(q_mode == I2C_Q_REPLACE && node != bus->head)
                    node->desc = desc;
                else
                    rc = I2C_RC_NOTADDED;
                goto _exit;
            }
        }
    }

    /* Try to claim a queue node without blocking if we can */
    if(semaphore_wait(&bus->sema, TIMEOUT_NOBLOCK) != OBJ_WAIT_SUCCEEDED) {
        /* Bail out now if caller doesn't want to wait */
        if(timeout == TIMEOUT_NOBLOCK) {
            rc = I2C_RC_BUSY;
            goto _exit;
        }

        /* Wait on the semaphore */
        corelock_unlock(&bus->cl);
        restore_irq(irq);
        if(semaphore_wait(&bus->sema, timeout) == OBJ_WAIT_TIMEDOUT)
            return I2C_RC_BUSY;

        /* Got a node; re-lock */
        irq = disable_irq_save();
        corelock_lock(&bus->cl);
    }

    /* Alloc the node and push it to queue */
    node = i2c_async_popfree(&bus->free);
    node->desc = desc;
    node->cookie = cookie;
    i2c_async_bus_enqueue(bus, node);

    /* Start the first descriptor if the bus is idle */
    if(node == bus->head)
        __i2c_async_submit(busnr, desc);

  _exit:
    corelock_unlock(&bus->cl);
    restore_irq(irq);
    return rc;
}

int i2c_async_cancel(int busnr, int cookie)
{
    i2c_async_bus* bus = &i2c_async_busses[busnr];
    int rc = I2C_RC_NOTFOUND;

    int irq = disable_irq_save();
    corelock_lock(&bus->cl);

    /* Bail if queue is empty */
    if(!bus->head)
        goto _exit;

    /* Check the running transaction for a match */
    if(bus->head->cookie == cookie) {
        rc = I2C_RC_BUSY;
        goto _exit;
    }

    /* Walk the queue, starting after the head */
    i2c_async_txn* prev = bus->head;
    i2c_async_txn* node = prev->next;
    while(node) {
        if(node->cookie == cookie) {
            prev->next = node->next;
            rc = I2C_RC_OK;
            goto _exit;
        }

        prev = node;
        node = node->next;
    }

  _exit:
    corelock_unlock(&bus->cl);
    restore_irq(irq);
    return rc;
}

int i2c_async_reserve_cookies(int busnr, int count)
{
    i2c_async_bus* bus = &i2c_async_busses[busnr];
    int ret = bus->nextcookie;
    bus->nextcookie += count;
    return ret;
}

static void i2c_sync_callback(int status, i2c_descriptor* d)
{
    struct semaphore* sem = (struct semaphore*)d->arg;
    d->arg = status;
    semaphore_release(sem);
}

static void i2c_reg_modify1_callback(int status, i2c_descriptor* d)
{
    if(status == I2C_STATUS_OK) {
        uint8_t* buf = (uint8_t*)d->buffer[1];
        uint8_t val = *buf;
        *buf = (val & ~(d->arg >> 8)) | (d->arg & 0xff);
        d->arg = val;
    }
}

int i2c_reg_write(int bus, uint8_t addr, uint8_t reg,
                  int count, const uint8_t* buf)
{
    struct semaphore sem;
    semaphore_init(&sem, 1, 0);

    i2c_descriptor desc;
    desc.slave_addr = addr;
    desc.bus_cond = I2C_START | I2C_STOP;
    desc.tran_mode = I2C_WRITE;
    desc.buffer[0] = &reg;
    desc.count[0] = 1;
    desc.buffer[1] = (uint8_t*)buf;
    desc.count[1] = count;
    desc.callback = &i2c_sync_callback;
    desc.arg = (intptr_t)&sem;
    desc.next = NULL;

    i2c_async_queue(bus, TIMEOUT_BLOCK, I2C_Q_ADD, 0, &desc);
    semaphore_wait(&sem, TIMEOUT_BLOCK);

    return desc.arg;
}

int i2c_reg_read(int bus, uint8_t addr, uint8_t reg,
                 int count, uint8_t* buf)
{
    struct semaphore sem;
    semaphore_init(&sem, 1, 0);

    i2c_descriptor desc;
    desc.slave_addr = addr;
    desc.bus_cond = I2C_START | I2C_STOP;
    desc.tran_mode = I2C_READ;
    desc.buffer[0] = &reg;
    desc.count[0] = 1;
    desc.buffer[1] = buf;
    desc.count[1] = count;
    desc.callback = &i2c_sync_callback;
    desc.arg = (intptr_t)&sem;
    desc.next = NULL;

    i2c_async_queue(bus, TIMEOUT_BLOCK, I2C_Q_ADD, 0, &desc);
    semaphore_wait(&sem, TIMEOUT_BLOCK);

    return desc.arg;
}

int i2c_reg_modify1(int bus, uint8_t addr, uint8_t reg,
                    uint8_t clr, uint8_t set, uint8_t* val)
{
    struct semaphore sem;
    semaphore_init(&sem, 1, 0);

    uint8_t buf[2];
    buf[0] = reg;

    i2c_descriptor desc[2];
    desc[0].slave_addr = addr;
    desc[0].bus_cond = I2C_START | I2C_STOP;
    desc[0].tran_mode = I2C_READ;
    desc[0].buffer[0] = &buf[0];
    desc[0].count[0] = 1;
    desc[0].buffer[1] = &buf[1];
    desc[0].count[1] = 1;
    desc[0].callback = &i2c_reg_modify1_callback;
    desc[0].arg = (clr << 8) | set;
    desc[0].next = &desc[1];

    desc[1].slave_addr = addr;
    desc[1].bus_cond = I2C_START | I2C_STOP;
    desc[1].tran_mode = I2C_WRITE;
    desc[1].buffer[0] = &buf[0];
    desc[1].count[0] = 2;
    desc[1].buffer[1] = NULL;
    desc[1].count[1] = 0;
    desc[1].callback = &i2c_sync_callback;
    desc[1].arg = (intptr_t)&sem;
    desc[1].next = NULL;

    i2c_async_queue(bus, TIMEOUT_BLOCK, I2C_Q_ADD, 0, &desc[0]);
    semaphore_wait(&sem, TIMEOUT_BLOCK);

    if(val && desc[1].arg == I2C_STATUS_OK)
        *val = desc[0].arg;

    return desc[1].arg;
}
