/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright © 2009 Bertrik Sikken
 *
 * 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 "s5l87xx.h"
#include "dma-target.h"
#include "panic.h"
#include "system.h"

/*  Driver for the IODMA part of the s5l8700

    When requesting a DMA transfer the supplied callback is stored and called
    upon completion of the DMA transfer (callback runs in interrupt context).
 */


#define DMAC_BASE       0x38400000

#define DMABASE(c)  (*(volatile unsigned int*)(DMAC_BASE+0x00+(0x20*c)))
#define DMACON(c)   (*(volatile unsigned int*)(DMAC_BASE+0x04+(0x20*c)))
#define DMATCNT(c)  (*(volatile unsigned int*)(DMAC_BASE+0x08+(0x20*c)))
#define DMACADDR(c) (*(volatile unsigned int*)(DMAC_BASE+0x0C+(0x20*c)))
#define DMACTCNT(c) (*(volatile unsigned int*)(DMAC_BASE+0x10+(0x20*c)))
#define DMACOM(c)   (*(volatile unsigned int*)(DMAC_BASE+0x14+(0x20*c)))
#define DMANOFF(c)  (*(volatile unsigned int*)(DMAC_BASE+0x18+(0x20*c)))

#define DMACOM_HOLD             2   /* only allowed on channel 0 */
#define DMACOM_SKIP             3   /* only allowed on channel 0 */
#define DMACOM_CHAN_ON          4
#define DMACOM_CHAN_OFF         5
#define DMACOM_CLEAR_HCOM       6
#define DMACOM_CLEAR_HCOM_WCOM  7


/* one completion callback for each channel */
static void (*dma_callback[4])(void);


void dma_init(void)
{
    int i;
    
    for (i = 0; i < 4; i++) {
        dma_callback[i] = NULL;
        dma_disable_channel(i);
    }

    INTMSK |= (1 << 10);
}

/* setup a DMA transfer, but do not start it yet */
void dma_setup_channel(int channel, int sel, int dir, int dsize, int blen,
                       void *addr, size_t size, void (*callback)(void))
{
    dma_callback[channel] = callback;

    DMACON(channel) = (sel << 30) |     /* DEVSEL */
                      (dir << 29) |     /* DIR */
                      (0 << 24) |       /* SCHCNT */
                      (dsize << 22) |   /* DSIZE */
                      (blen << 19) |    /* BLEN */
                      (0 << 18) |       /* RELOAD */
                      (0 << 17) |       /* HCOMINT */
                      (1 << 16) |       /* WCOMINT */
                      (0 << 0);         /* OFFSET */
    DMABASE(channel) = (unsigned int)addr;
    DMATCNT(channel) = size - 1;
}

void dma_enable_channel(int channel)
{
    DMACOM(channel) = DMACOM_CHAN_ON;
}

void dma_disable_channel(int channel)
{
    DMACOM(channel) = DMACOM_CHAN_OFF;
}

/* interrupt handler for all DMA channels */
void INT_DMA(void)
{
    unsigned int mask;
    int channel;

    mask = (1 << 0) |   /* WCOMx interrupt bit */
           (1 << 1);    /* HCOMx interrupt bit */
    for (channel = 0; channel < 4; channel++) {
        if (DMAALLST & mask) {
            /* clear half and whole completion bits */
            DMACOM(channel) = DMACOM_CLEAR_HCOM_WCOM;

            if (dma_callback[channel]) {
                dma_callback[channel]();
            }
        }
        mask <<= 4;
    }
}

