/******************************************************************************
 * The MIT License
 *
 * Copyright (c) 2010 Michael Hope.
 * 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/stm32f1/dma.c
 * @author Marti Bolivar <mbolivar@leaflabs.com>;
 *         Original implementation by Michael Hope
 * @brief STM32F1 DMA support.
 */

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

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

/*
 * Devices
 */

static dma_dev dma1 = {
    .regs     = DMA1_BASE,
    .clk_id   = RCC_DMA1,
    .handlers = {{ .handler = NULL, .irq_line = NVIC_DMA_CH1 },
                 { .handler = NULL, .irq_line = NVIC_DMA_CH2 },
                 { .handler = NULL, .irq_line = NVIC_DMA_CH3 },
                 { .handler = NULL, .irq_line = NVIC_DMA_CH4 },
                 { .handler = NULL, .irq_line = NVIC_DMA_CH5 },
                 { .handler = NULL, .irq_line = NVIC_DMA_CH6 },
                 { .handler = NULL, .irq_line = NVIC_DMA_CH7 }},
};
/** STM32F1 DMA1 device */
dma_dev *DMA1 = &dma1;

#if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)
static dma_dev dma2 = {
    .regs     = DMA2_BASE,
    .clk_id   = RCC_DMA2,
    .handlers = {{ .handler = NULL, .irq_line = NVIC_DMA2_CH1   },
                 { .handler = NULL, .irq_line = NVIC_DMA2_CH2   },
                 { .handler = NULL, .irq_line = NVIC_DMA2_CH3   },
                 { .handler = NULL, .irq_line = NVIC_DMA2_CH_4_5 },
                 { .handler = NULL, .irq_line = NVIC_DMA2_CH_4_5 }}, /* !@#$ */
};
/** STM32F1 DMA2 device */
dma_dev *DMA2 = &dma2;
#endif

/*
 * Auxiliary routines
 */

/* Can channel serve cfg->tube_req_src? */
static int cfg_req_ok(dma_channel channel, dma_tube_config *cfg) {
    return (cfg->tube_req_src & 0x7) == channel;
}

/* Can dev serve cfg->tube_req_src? */
static int cfg_dev_ok(dma_dev *dev, dma_tube_config *cfg) {
    return (rcc_clk_id)(cfg->tube_req_src >> 3) == dev->clk_id;
}

/* 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 the direction implied by src->dst supported? */
static int cfg_dir_ok(dma_tube_config *cfg) {
    /* We can't do peripheral->peripheral transfers. */
    return ((_dma_addr_type(cfg->tube_src) == DMA_ATYPE_MEM) ||
            (_dma_addr_type(cfg->tube_dst) == DMA_ATYPE_MEM));
}

static int preconfig_check(dma_dev *dev, dma_channel channel,
                           dma_tube_config *cfg) {
    if (!cfg_req_ok(channel, cfg)) {
        return -DMA_TUBE_CFG_EREQ;
    }
    if (cfg->tube_nr_xfers > 65535) {
        return -DMA_TUBE_CFG_ENDATA;
    }
    if (!cfg_dev_ok(dev, cfg)) {
        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(cfg)) {
        return -DMA_TUBE_CFG_EDIR;
    }
    return DMA_TUBE_CFG_SUCCESS;
}

static inline void set_ccr(dma_tube_reg_map *chregs,
                           dma_xfer_size msize, int minc,
                           dma_xfer_size psize, int pinc,
                           uint32 other_flags) {
    chregs->CCR = ((msize << 10) | (psize << 8) |
                   (minc ? DMA_CCR_MINC : 0) | (pinc ? DMA_CCR_PINC : 0) |
                   other_flags);
}

static inline uint32 cfg_ccr_flags(unsigned tube_flags) {
    /* DMA_CFG_SRC_INC and DMA_CFG_DST_INC are special */
    return tube_flags & ~(DMA_CFG_SRC_INC | DMA_CFG_DST_INC);
}

/* Configure chregs according to cfg, where cfg->tube_dst is peripheral. */
static int config_to_per(dma_tube_reg_map *chregs, dma_tube_config *cfg) {
    /* Check that ->tube_src is memory (if it's anything else, we
     * shouldn't have been called). */
    ASSERT(_dma_addr_type(cfg->tube_src) == DMA_ATYPE_MEM);

    set_ccr(chregs,
            cfg->tube_src_size, cfg->tube_flags & DMA_CFG_SRC_INC,
            cfg->tube_dst_size, cfg->tube_flags & DMA_CFG_DST_INC,
            (cfg_ccr_flags(cfg->tube_flags) | DMA_CCR_DIR_FROM_MEM));
    chregs->CNDTR = cfg->tube_nr_xfers;
    chregs->CMAR = (uint32)cfg->tube_src;
    chregs->CPAR = (uint32)cfg->tube_dst;
    return DMA_TUBE_CFG_SUCCESS;
}

/* Configure chregs according to cfg, where cfg->tube_dst is memory. */
static int config_to_mem(dma_tube_reg_map *chregs, dma_tube_config *cfg) {
    uint32 mem2mem;

    if ((_dma_addr_type(cfg->tube_src) == DMA_ATYPE_MEM) &&
        (cfg->tube_flags & DMA_CFG_CIRC)) {
        /* Can't do mem-to-mem and circular mode */
        return -DMA_TUBE_CFG_ECFG;
    }

    mem2mem = (_dma_addr_type(cfg->tube_src) == DMA_ATYPE_MEM ?
               DMA_CCR_MEM2MEM : 0);
    set_ccr(chregs,
            cfg->tube_dst_size, cfg->tube_flags & DMA_CFG_DST_INC,
            cfg->tube_src_size, cfg->tube_flags & DMA_CFG_SRC_INC,
            (cfg_ccr_flags(cfg->tube_flags) |
             DMA_CCR_DIR_FROM_PER |
             mem2mem));
    chregs->CNDTR = cfg->tube_nr_xfers;
    chregs->CMAR = (uint32)cfg->tube_dst;
    chregs->CPAR = (uint32)cfg->tube_src;
    return DMA_TUBE_CFG_SUCCESS;
}

/*
 * Routines
 */

int dma_tube_cfg(dma_dev *dev, dma_channel channel, dma_tube_config *cfg) {
    dma_tube_reg_map *chregs;
    int ret = preconfig_check(dev, channel, cfg);

    if (ret < 0) {
        return ret;
    }

    dma_disable(dev, channel);        /* Must disable before reconfiguring */
    dma_clear_isr_bits(dev, channel); /* For sanity and consistency
                                       * with STM32F2. */

    chregs = dma_tube_regs(dev, channel);
    switch (_dma_addr_type(cfg->tube_dst)) {
    case DMA_ATYPE_PER:
        ret = config_to_per(chregs, cfg);
        break;
    case DMA_ATYPE_MEM:
        ret = config_to_mem(chregs, cfg);
        break;
    default:
        /* Can't happen */
        ASSERT(0);
        return -DMA_TUBE_CFG_ECFG;
    }
    if (ret < 0) {
        return ret;
    }
    chregs->CNDTR = cfg->tube_nr_xfers;
    return DMA_TUBE_CFG_SUCCESS;
}

void dma_set_priority(dma_dev *dev,
                      dma_channel channel,
                      dma_priority priority) {
    dma_channel_reg_map *channel_regs;
    uint32 ccr;

    ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));

    channel_regs = dma_channel_regs(dev, channel);
    ccr = channel_regs->CCR;
    ccr &= ~DMA_CCR_PL;
    ccr |= (priority << 12);
    channel_regs->CCR = ccr;
}

void dma_set_num_transfers(dma_dev *dev,
                           dma_channel channel,
                           uint16 num_transfers) {
    dma_channel_reg_map *channel_regs;

    ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));

    channel_regs = dma_channel_regs(dev, channel);
    channel_regs->CNDTR = num_transfers;
}

void dma_attach_interrupt(dma_dev *dev, dma_channel channel,
                          void (*handler)(void)) {
    DMA_GET_HANDLER(dev, channel) = handler;
    nvic_irq_enable(dev->handlers[channel - 1].irq_line);
}

void dma_detach_interrupt(dma_dev *dev, dma_channel channel) {
    /* Don't use nvic_irq_disable()! Think about DMA2 channels 4 and 5. */
    dma_channel_regs(dev, channel)->CCR &= ~0xF;
    DMA_GET_HANDLER(dev, channel) = NULL;
}

void dma_enable(dma_dev *dev, dma_channel channel) {
    dma_channel_reg_map *chan_regs = dma_channel_regs(dev, channel);
    bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_EN_BIT, 1);
}

void dma_disable(dma_dev *dev, dma_channel channel) {
    dma_channel_reg_map *chan_regs = dma_channel_regs(dev, channel);
    bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_EN_BIT, 0);
}

dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_channel channel) {
    /* Grab and clear the ISR bits. */
    uint8 status_bits = dma_get_isr_bits(dev, channel);
    dma_clear_isr_bits(dev, channel);

    /* If the channel global interrupt flag is cleared, then
     * something's very wrong. */
    ASSERT(status_bits & 0x1);
    /* If GIF is set, then some other flag should be set, barring
     * something unexpected (e.g. the user making an unforeseen IFCR
     * write). */
    ASSERT(status_bits != 0x1);

    /* ISR flags get set even if the corresponding interrupt enable
     * bits in the channel's configuration register are cleared, so we
     * can't use a switch here.
     *
     * Don't change the order of these if statements. */
    if (status_bits & 0x8) {
        return DMA_TRANSFER_ERROR;
    } else if (status_bits & 0x2) {
        return DMA_TRANSFER_COMPLETE;
    } else if (status_bits & 0x4) {
        return DMA_TRANSFER_HALF_COMPLETE;
    }

    /* If we get here, one of our assumptions has been violated, but
     * the debug level is too low for the above ASSERTs() to have had
     * any effect. In order to fail fast, mimic the DMA controller's
     * behavior when an error occurs. */
    dma_disable(dev, channel);
    return DMA_TRANSFER_ERROR;
}

void dma_set_mem_addr(dma_dev *dev, dma_channel channel, __io void *addr) {
    dma_channel_reg_map *chan_regs;

    ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));

    chan_regs = dma_channel_regs(dev, channel);
    chan_regs->CMAR = (uint32)addr;
}

void dma_set_per_addr(dma_dev *dev, dma_channel channel, __io void *addr) {
    dma_channel_reg_map *chan_regs;

    ASSERT_FAULT(!dma_is_channel_enabled(dev, channel));

    chan_regs = dma_channel_regs(dev, channel);
    chan_regs->CPAR = (uint32)addr;
}

/**
 * @brief Deprecated. Use dma_tube_cfg() instead.
 *
 * Set up a DMA transfer.
 *
 * The channel will be disabled before being reconfigured.  The
 * transfer will have low priority by default.  You may choose another
 * priority before the transfer begins using dma_set_priority(), as
 * well as performing any other configuration you desire.  When the
 * channel is configured to your liking, enable it using dma_enable().
 *
 * @param dev DMA device.
 * @param channel DMA channel.
 * @param peripheral_address Base address of peripheral data register
 *                           involved in the transfer.
 * @param peripheral_size Peripheral data transfer size.
 * @param memory_address Base memory address involved in the transfer.
 * @param memory_size Memory data transfer size.
 * @param mode Logical OR of dma_mode_flags
 *
 * @see dma_tube_cfg()
 *
 * @sideeffect Disables the given DMA channel.
 * @see dma_xfer_size
 * @see dma_mode_flags
 * @see dma_set_num_transfers()
 * @see dma_set_priority()
 * @see dma_attach_interrupt()
 * @see dma_enable()
 */
__deprecated
void dma_setup_transfer(dma_dev       *dev,
                        dma_channel    channel,
                        __io void     *peripheral_address,
                        dma_xfer_size  peripheral_size,
                        __io void     *memory_address,
                        dma_xfer_size  memory_size,
                        uint32         mode) {
    dma_channel_reg_map *channel_regs = dma_channel_regs(dev, channel);

    dma_disable(dev, channel);  /* can't write to CMAR/CPAR otherwise */
    channel_regs->CCR = (memory_size << 10) | (peripheral_size << 8) | mode;
    channel_regs->CMAR = (uint32)memory_address;
    channel_regs->CPAR = (uint32)peripheral_address;
}

/*
 * IRQ handlers
 */

void __irq_dma1_channel1(void) {
    dma_irq_handler(DMA1, DMA_CH1);
}

void __irq_dma1_channel2(void) {
    dma_irq_handler(DMA1, DMA_CH2);
}

void __irq_dma1_channel3(void) {
    dma_irq_handler(DMA1, DMA_CH3);
}

void __irq_dma1_channel4(void) {
    dma_irq_handler(DMA1, DMA_CH4);
}

void __irq_dma1_channel5(void) {
    dma_irq_handler(DMA1, DMA_CH5);
}

void __irq_dma1_channel6(void) {
    dma_irq_handler(DMA1, DMA_CH6);
}

void __irq_dma1_channel7(void) {
    dma_irq_handler(DMA1, DMA_CH7);
}

#if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)
void __irq_dma2_channel1(void) {
    dma_irq_handler(DMA2, DMA_CH1);
}

void __irq_dma2_channel2(void) {
    dma_irq_handler(DMA2, DMA_CH2);
}

void __irq_dma2_channel3(void) {
    dma_irq_handler(DMA2, DMA_CH3);
}

void __irq_dma2_channel4_5(void) {
    if ((DMA2_BASE->CCR4 & DMA_CCR_EN) && (DMA2_BASE->ISR & DMA_ISR_GIF4)) {
        dma_irq_handler(DMA2, DMA_CH4);
    }
    if ((DMA2_BASE->CCR5 & DMA_CCR_EN) && (DMA2_BASE->ISR & DMA_ISR_GIF5)) {
        dma_irq_handler(DMA2, DMA_CH5);
    }
}
#endif
