/******************************************************************************
 * The MIT License
 *
 * Copyright (c) 2012 LeafLabs, LLC.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************/

/**
 * @file libmaple/stm32f2/dma.c
 * @author Marti Bolivar <mbolivar@leaflabs.com>
 * @brief STM32F2 DMA support.
 */

#include <libmaple/dma.h>
#include <libmaple/bitband.h>
#include <libmaple/util.h>

/* Hack to ensure inlining in dma_irq_handler() */
#define DMA_GET_HANDLER(dev, tube) (dev->handlers[tube].handler)
#include "dma_private.h"

/*
 * Devices
 */

static dma_dev dma1 = {
    .regs = DMA1_BASE,
    .clk_id = RCC_DMA1,
    .handlers = {{ .handler = NULL, .irq_line = NVIC_DMA1_STREAM0 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM1 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM2 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM3 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM4 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM5 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM6 },
                 { .handler = NULL, .irq_line = NVIC_DMA1_STREAM7 }},
};
dma_dev *DMA1 = &dma1;

static dma_dev dma2 = {
    .regs = DMA2_BASE,
    .clk_id = RCC_DMA2,
    .handlers = {{ .handler = NULL, .irq_line = NVIC_DMA2_STREAM0 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM1 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM2 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM3 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM4 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM5 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM6 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_STREAM7 }},
};
dma_dev *DMA2 = &dma2;

/*
 * Helpers for dealing with dma_request_src's bit encoding (see the
 * comments in the dma_request_src definition).
 */

/* rcc_clk_id of dma_dev which supports src. */
static __always_inline rcc_clk_id src_clk_id(dma_request_src src) {
    return (rcc_clk_id)(((uint32)src >> 3) & 0x3F);
}

/* Bit vector of streams supporting src (e.g., bit 0 set => DMA_S0 support). */
static __always_inline uint32 src_stream_mask(dma_request_src src) {
    return ((uint32)src >> 10) & 0xFF;
}

/* Channel corresponding to src. */
static __always_inline dma_channel src_channel(dma_request_src src) {
    return (dma_channel)(src & 0x7);
}

/*
 * Routines
 */

/* For convenience */
#define ASSERT_NOT_ENABLED(dev, tube) ASSERT(!dma_is_enabled(dev, tube))

/* Helpers for dma_tube_cfg() */
static int preconfig_check(dma_dev *dev, dma_tube tube, dma_tube_config *cfg);
static int postconfig_check(dma_tube_reg_map *dummy, dma_tube_config *cfg);
static int config_fifo(dma_tube_reg_map *dummy, dma_tube_config *cfg);
static int config_src_dst(dma_tube_reg_map *dummy, dma_tube_config *cfg);
static void copy_regs(dma_tube_reg_map *src, dma_tube_reg_map *dst);

int dma_tube_cfg(dma_dev *dev, dma_tube tube, dma_tube_config *cfg) {
    dma_tube_reg_map dummy_regs;
    dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
    int ret;

    /* Initial error checking. */
    ret = preconfig_check(dev, tube, cfg);
    if (ret < 0) {
        return ret;
    }

    /* Disable `tube' as per RM0033. */
    dma_disable(dev, tube);
    dma_clear_isr_bits(dev, tube);

    /* Don't write to tregs until we've decided `cfg' is really OK,
     * so as not to make a half-formed mess if we have to error out. */
    copy_regs(tregs, &dummy_regs);

    /* Try to reconfigure `tube', bailing on error. */
    ret = config_fifo(&dummy_regs, cfg);
    if (ret < 0) {
        return ret;
    }
    ret = config_src_dst(&dummy_regs, cfg);
    if (ret < 0) {
        return ret;
    }
    dummy_regs.SNDTR = cfg->tube_nr_xfers;
    ret = postconfig_check(&dummy_regs, cfg);
    if (ret < 0) {
        return ret;
    }

    /* Ok, we're good. Commit to the new configuration. */
    copy_regs(&dummy_regs, tregs);
    return ret;
}

void dma_set_priority(dma_dev *dev, dma_stream stream, dma_priority priority) {
    dma_tube_reg_map *tregs = dma_tube_regs(dev, stream);
    uint32 scr;
    ASSERT_NOT_ENABLED(dev, stream);
    scr = tregs->SCR;
    scr &= ~DMA_SCR_PL;
    scr |= (priority << 16);
    tregs->SCR = scr;
}

void dma_set_num_transfers(dma_dev *dev, dma_tube tube, uint16 num_transfers) {
    dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
    ASSERT_NOT_ENABLED(dev, tube);
    tregs->SNDTR = num_transfers;
}

/**
 * @brief Set memory 0 or memory 1 address.
 *
 * This is a general function for setting one of the two memory
 * addresses available on the double-buffered STM32F2 DMA controllers.
 *
 * @param dev     DMA device
 * @param tube    Tube on dev.
 * @param n       If 0, set memory 0 address. If 1, set memory 1 address.
 * @param address Address to set
 */
void dma_set_mem_n_addr(dma_dev *dev, dma_tube tube, int n,
                        __io void *address) {
    dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
    uint32 addr = (uint32)address;

    ASSERT_NOT_ENABLED(dev, tube);
    if (n) {
        tregs->SM1AR = addr;
    } else {
        tregs->SM0AR = addr;
    }
}

void dma_set_per_addr(dma_dev *dev, dma_tube tube, __io void *address) {
    dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
    ASSERT_NOT_ENABLED(dev, tube);
    tregs->SPAR = (uint32)address;
}

/**
 * @brief Enable a stream's FIFO.
 *
 * You may only call this function when the stream is disabled.
 *
 * @param dev  DMA device
 * @param tube Stream whose FIFO to enable.
 */
void dma_enable_fifo(dma_dev *dev, dma_tube tube) {
    ASSERT_NOT_ENABLED(dev, tube);
    bb_peri_set_bit(&(dma_tube_regs(dev, tube)->SFCR), DMA_SFCR_DMDIS_BIT, 1);
}

/**
 * @brief Disable a stream's FIFO.
 *
 * You may only call this function when the stream is disabled.
 *
 * @param dev  DMA device
 * @param tube Stream whose FIFO to disable.
 */
void dma_disable_fifo(dma_dev *dev, dma_tube tube) {
    ASSERT_NOT_ENABLED(dev, tube);
    bb_peri_set_bit(&(dma_tube_regs(dev, tube)->SFCR), DMA_SFCR_DMDIS_BIT, 0);
}

void dma_attach_interrupt(dma_dev *dev, dma_tube tube,
                          void (*handler)(void)) {
    dev->handlers[tube].handler = handler;
    nvic_irq_enable(dev->handlers[tube].irq_line);
}

void dma_detach_interrupt(dma_dev *dev, dma_tube tube) {
    nvic_irq_disable(dev->handlers[tube].irq_line);
    dev->handlers[tube].handler = NULL;
}

void dma_enable(dma_dev *dev, dma_tube tube) {
    dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
    bb_peri_set_bit(&tregs->SCR, DMA_SCR_EN_BIT, 1);
}

void dma_disable(dma_dev *dev, dma_tube tube) {
    dma_tube_reg_map *tregs = dma_tube_regs(dev, tube);
    bb_peri_set_bit(&tregs->SCR, DMA_SCR_EN_BIT, 0);
    /* The stream might not get disabled immediately, so wait. */
    while (tregs->SCR & DMA_SCR_EN)
        ;
}

dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_tube tube) {
    /* TODO: does it still make sense to have this function? We should
     * probably just be returning the ISR bits, with some defines to
     * pull the flags out. The lack of masked status bits is an
     * annoyance that would require documentation to solve, though. */
    uint8 status_bits = dma_get_isr_bits(dev, tube);
    dma_clear_isr_bits(dev, tube);
    ASSERT(status_bits);        /* Or something's very wrong */
    /* Don't change the order of these if statements. */
    if (status_bits & 0x0) {
        return DMA_TRANSFER_FIFO_ERROR;
    } else if (status_bits & 0x4) {
        return DMA_TRANSFER_DME_ERROR;
    } else if (status_bits & 0x8) {
        return DMA_TRANSFER_ERROR;
    } else if (status_bits & 0x20) {
        return DMA_TRANSFER_COMPLETE;
    } else if (status_bits & 0x10) {
        return DMA_TRANSFER_HALF_COMPLETE;
    }

    /* Something's wrong; one of those bits should have been set. Fail
     * an assert, and mimic the error behavior in case of a high debug
     * level. */
    ASSERT(0);
    dma_disable(dev, tube);
    return DMA_TRANSFER_ERROR;
}

/*
 * IRQ handlers
 */

void __irq_dma1_stream0(void) {
    dma_irq_handler(DMA1, DMA_S0);
}

void __irq_dma1_stream1(void) {
    dma_irq_handler(DMA1, DMA_S1);
}

void __irq_dma1_stream2(void) {
    dma_irq_handler(DMA1, DMA_S2);
}

void __irq_dma1_stream3(void) {
    dma_irq_handler(DMA1, DMA_S3);
}

void __irq_dma1_stream4(void) {
    dma_irq_handler(DMA1, DMA_S4);
}

void __irq_dma1_stream5(void) {
    dma_irq_handler(DMA1, DMA_S5);
}

void __irq_dma1_stream6(void) {
    dma_irq_handler(DMA1, DMA_S6);
}

void __irq_dma1_stream7(void) {
    dma_irq_handler(DMA1, DMA_S7);
}

void __irq_dma2_stream0(void) {
    dma_irq_handler(DMA2, DMA_S0);
}

void __irq_dma2_stream1(void) {
    dma_irq_handler(DMA2, DMA_S1);
}

void __irq_dma2_stream2(void) {
    dma_irq_handler(DMA2, DMA_S2);
}

void __irq_dma2_stream3(void) {
    dma_irq_handler(DMA2, DMA_S3);
}

void __irq_dma2_stream4(void) {
    dma_irq_handler(DMA2, DMA_S4);
}

void __irq_dma2_stream5(void) {
    dma_irq_handler(DMA2, DMA_S5);
}

void __irq_dma2_stream6(void) {
    dma_irq_handler(DMA2, DMA_S6);
}

void __irq_dma2_stream7(void) {
    dma_irq_handler(DMA2, DMA_S7);
}

/*
 * Auxiliary routines for dma_tube_cfg()
 */

/* Is addr acceptable for use as DMA src/dst? */
static int cfg_mem_ok(__io void *addr) {
    enum dma_atype atype = _dma_addr_type(addr);
    return atype == DMA_ATYPE_MEM || atype == DMA_ATYPE_PER;
}

/* Is src -> dst a reasonable combination of [MEM,PER] -> [MEM,PER]? */
static int cfg_dir_ok(dma_dev *dev, __io void *src, __io void *dst) {
    switch (_dma_addr_type(dst)) {
    case DMA_ATYPE_MEM:
        /* Only DMA2 can do memory-to-memory */
        return ((_dma_addr_type(src) == DMA_ATYPE_PER) ||
                (dev->clk_id == RCC_DMA2));
    case DMA_ATYPE_PER:
        /* Peripheral-to-peripheral is illegal */
        return _dma_addr_type(src) == DMA_ATYPE_PER;
    default: /* Can't happen */
        ASSERT(0);
        return 0;
    }
}

/* Initial sanity check for dma_tube_cfg() */
static int preconfig_check(dma_dev *dev, dma_tube tube,
                           dma_tube_config *cfg) {
    if (!(src_stream_mask(cfg->tube_req_src) & (1U << tube))) {
        /* ->tube_req_src not supported by stream */
        return -DMA_TUBE_CFG_EREQ;
    }
    if (cfg->tube_nr_xfers > 65535) {
        /* That's too many. */
        return -DMA_TUBE_CFG_ENDATA;
    }
    if (src_clk_id(cfg->tube_req_src) != dev->clk_id) {
        /* ->tube_req_src not supported by dev */
        return -DMA_TUBE_CFG_EDEV;
    }
    if (!cfg_mem_ok(cfg->tube_src)) {
        return -DMA_TUBE_CFG_ESRC;
    }
    if (!cfg_mem_ok(cfg->tube_dst)) {
        return -DMA_TUBE_CFG_EDST;
    }
    if (!cfg_dir_ok(dev, cfg->tube_src, cfg->tube_dst)) {
        return -DMA_TUBE_CFG_EDIR;
    }
    return DMA_TUBE_CFG_SUCCESS;
}

static int config_fifo(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
    /* TODO: FIFO configuration based on cfg->target_data */
    uint32 sfcr = dummy->SFCR;
    sfcr &= ~DMA_SFCR_FEIE;
    sfcr |= (cfg->tube_flags & DMA_CFG_FIFO_ERR_IE) ? DMA_SFCR_FEIE : 0;
    dummy->SFCR = sfcr;
    return DMA_TUBE_CFG_SUCCESS;
}

/* Helper for configuring (DMA_SxCR) */
#define BITS_WE_CARE_ABOUT                                              \
    (DMA_SCR_CHSEL | DMA_SCR_MBURST | DMA_SCR_PBURST | DMA_SCR_PINCOS | \
     DMA_SCR_MINC | DMA_SCR_PINC | DMA_SCR_CIRC | DMA_SCR_DIR |         \
     DMA_SCR_PFCTRL | DMA_SCR_TCIE | DMA_SCR_HTIE | DMA_SCR_TEIE |      \
     DMA_SCR_DMEIE)
static inline void config_scr(dma_tube_reg_map *dummy, dma_tube_config *cfg,
                              unsigned src_shift, uint32 src_inc,
                              unsigned dst_shift, uint32 dst_inc,
                              uint32 dir) {
    /* These would go here if we supported them: MBURST, PBURST,
     * PINCOS, PFCTRL. We explicitly choose low priority, and double
     * buffering belongs elsewhere, I think. [mbolivar] */
    uint32 flags = cfg->tube_flags & BITS_WE_CARE_ABOUT;
    uint32 scr = dummy->SCR;
    scr &= ~(BITS_WE_CARE_ABOUT | DMA_SCR_PL);
    scr |= (/* CHSEL */
            (src_channel(cfg->tube_req_src) << 25) |
            /* MSIZE/PSIZE */
            (cfg->tube_src_size << src_shift) |
            (cfg->tube_dst_size << dst_shift) |
            /* MINC/PINC */
            ((cfg->tube_flags & DMA_CFG_SRC_INC) ? src_inc : 0) |
            ((cfg->tube_flags & DMA_CFG_DST_INC) ? dst_inc : 0) |
            /* DIR */
            dir |
            /* Other flags carried by cfg->tube_flags */
            flags);
    dummy->SCR = scr;
}
#undef BITS_WE_CARE_ABOUT

/* Helper for when cfg->tube_dst is memory */
static int config_to_mem(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
    uint32 dir = (_dma_addr_type(cfg->tube_src) == DMA_ATYPE_MEM ?
                  DMA_SCR_DIR_MEM_TO_MEM : DMA_SCR_DIR_PER_TO_MEM);

    if ((dir == DMA_SCR_DIR_MEM_TO_MEM) && (cfg->tube_flags & DMA_CFG_CIRC)) {
        return -DMA_TUBE_CFG_ECFG; /* Can't do DMA_CFG_CIRC and mem->mem. */
    }

    config_scr(dummy, cfg, 11, DMA_SCR_PINC, 13, DMA_SCR_MINC, dir);
    dummy->SPAR = (uint32)cfg->tube_src;
    dummy->SM0AR = (uint32)cfg->tube_dst;
    return DMA_TUBE_CFG_SUCCESS;
}

/* Helper for when cfg->tube_src is peripheral */
static int config_to_per(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
    config_scr(dummy, cfg, 13, DMA_SCR_MINC, 11, DMA_SCR_PINC,
               DMA_SCR_DIR_MEM_TO_PER);
    dummy->SM0AR = (uint32)cfg->tube_src;
    dummy->SPAR = (uint32)cfg->tube_dst;
    return DMA_TUBE_CFG_SUCCESS;
}

/* Configures SCR, SPAR, SM0AR, and checks that the result is OK. */
static int config_src_dst(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
    switch (_dma_addr_type(cfg->tube_dst)) {
    case DMA_ATYPE_MEM:
        return config_to_mem(dummy, cfg);
    case DMA_ATYPE_PER:
        return config_to_per(dummy, cfg);
    case DMA_ATYPE_OTHER:
    default: /* shut up, GCC */
        /* Can't happen */
        ASSERT(0);
        return -DMA_TUBE_CFG_ECFG;
    }
}

/* Final checks we can only perform when fully configured */
static int postconfig_check(dma_tube_reg_map *dummy, dma_tube_config *cfg) {
    /* TODO add dma_get_[mem,per]_size() and use them here */
    /* msize and psize are in bytes here: */
    uint32 scr = dummy->SCR;
    uint32 msize = 1U << ((scr >> 13) & 0x3);
    uint32 psize = 1U << ((scr >> 11) & 0x3);

    /* Ensure NDT will work with PSIZE/MSIZE.
     *
     * RM0033 specifies that PSIZE, MSIZE, and NDT must be such that
     * the last transfer completes; i.e. that if PSIZE < MSIZE, then
     * NDT is a multiple of MSIZE/PSIZE.  See e.g. Table 27. */
    if ((psize < msize) && (cfg->tube_nr_xfers % (msize / psize))) {
        return -DMA_TUBE_CFG_ENDATA;
    }

    /* Direct mode is only possible if MSIZE == PSIZE. */
    if ((msize != psize) && !(dummy->SFCR & DMA_SFCR_DMDIS)) {
        return -DMA_TUBE_CFG_ESIZE;
    }

    return DMA_TUBE_CFG_SUCCESS;
}

/* Convenience for dealing with dummy registers */
static void copy_regs(dma_tube_reg_map *src, dma_tube_reg_map *dst) {
    dst->SCR = src->SCR;
    dst->SNDTR = src->SNDTR;
    dst->SPAR = src->SPAR;
    dst->SM0AR = src->SM0AR;
    dst->SFCR = src->SFCR;
}