root/sys/arch/arm/at91/at91pdcvar.h
/*      $Id: at91pdcvar.h,v 1.3 2012/11/12 18:00:36 skrll Exp $ */

#ifndef _AT91PDCVAR_H_
#define _AT91PDCVAR_H_

#include <arm/at91/at91pdcreg.h>
#include <sys/param.h>

#if     UNTESTED

typedef struct at91pdc_buf {
        void                    *buf_arg;       /* argument (mbuf or other data) */
        int                     buf_len;        /* length of data sent / recv */
        bus_dmamap_t            buf_dmamap;     /* dma map */
} at91pdc_buf_t;

typedef struct at91pdc_queue {
        at91pdc_buf_t           q_buf[2];       /* two buffers */
        unsigned                q_ndx;          /* buffer being sent (if q_len > 0) */
        unsigned                q_len;          /* number of buffers being used (<= 2) */
} at91pdc_queue_t;

#define AT91PDC_UNLOAD_BUF(_pq, _dmat, _sync, _free) do {       \
        unsigned        _i = (_pq)->q_ndx % 2;                  \
        at91pdc_buf_t   *_buf = &(_pq)->q_buf[i];               \
        void            *_arg = _buf->buf_arg;                  \
                                                                \
        if (_sync) {                                            \
                bus_dmamap_sync((_dmat), buf->buf_dmamap, 0,    \
                                buf->buf_len, (_sync));         \
        }                                                       \
        bus_dmamap_unload((_dmat), buf->buf_dmamap);            \
        buf->buf_arg = 0; buf->buf_len = 0;                     \
                                                                \
        (_pq)->q_ndx = i ^ 1;                                   \
        (_pq)->q_len--;                                         \
                                                                \
        (_free)(_arg);                                          \
} while (/*CONSTCOND*/0)

#define AT91PDC_UNLOAD_QUEUE(_pq, _dmat, _sync, _free, _idle)   \
do {                                                            \
        if ((_pq)->q_len > 1)                                   \
                AT91PDC_UNLOAD_BUF(_pq, _dmat, _sync, _free);   \
        if ((_idle) && (_pq)->q_len > 0)                        \
                AT91PDC_UNLOAD_BUF(_pq, _dmat, _sync, _free);   \
} while (/*CONSTCOND*/0)

#endif  /* UNTESTED */


typedef struct at91pdc_fifo {
        bus_dmamap_t    f_dmamap;       /* DMA map                      */
        void            *f_buf;         /* buffer address               */
        int             f_buf_size;     /* size of the fifo             */
        int             f_ndx;          /* current read/write index     */
        int             f_length;       /* number of bytes in fifo      */

        bus_addr_t      f_buf_addr;     /* buffer bus addr              */
        int             f_pdc_rd_ndx;   /* PDC read index               */
        int             f_pdc_wr_ndx;   /* PDC write index              */
        int             f_pdc_space;    /* number of bytes allocated for pdc */
} at91pdc_fifo_t;

static __inline int AT91PDC_FIFO_EMPTY(at91pdc_fifo_t *fifo)
{
        return fifo->f_length == 0;
}

static __inline int AT91PDC_FIFO_FULL(at91pdc_fifo_t *fifo)
{
        return fifo->f_length >= fifo->f_buf_size;
}

static __inline int AT91PDC_FIFO_SPACE(at91pdc_fifo_t *fifo)
{
        return fifo->f_buf_size - fifo->f_length;
}


static __inline void AT91PDC_RESET_FIFO(bus_space_tag_t iot,
                                         bus_space_handle_t ioh,
                                         bus_dma_tag_t dmat,
                                         uint offset,
                                         at91pdc_fifo_t *fifo,
                                         int rw)
{
        fifo->f_ndx = fifo->f_length = 0;

        fifo->f_pdc_rd_ndx = fifo->f_pdc_wr_ndx = 0;
        fifo->f_pdc_space = fifo->f_buf_size;

        if (!rw) {
                bus_space_write_4(iot, ioh, offset + PDC_RNCR, 0);
                bus_space_write_4(iot, ioh, offset + PDC_RCR, 0);
                bus_space_write_4(iot, ioh, offset + PDC_RNPR, fifo->f_buf_addr);
                bus_space_write_4(iot, ioh, offset + PDC_RPR, fifo->f_buf_addr);
        } else {
                bus_space_write_4(iot, ioh, offset + PDC_TNCR, 0);
                bus_space_write_4(iot, ioh, offset + PDC_TCR, 0);
                bus_space_write_4(iot, ioh, offset + PDC_TNPR, fifo->f_buf_addr);
                bus_space_write_4(iot, ioh, offset + PDC_TPR, fifo->f_buf_addr);
        }
}

static __inline int AT91PDC_FIFO_PREREAD(bus_space_tag_t iot,
                                          bus_space_handle_t ioh,
                                          bus_dma_tag_t dmat,
                                          uint offset,
                                          at91pdc_fifo_t *fifo,
                                          uint chunk_size)
{
        int al;
        int ret = 1;

        /* then check if we can queue new block */
        if (bus_space_read_4(iot, ioh, offset + PDC_RNCR))
                goto get_out;
        if (fifo->f_pdc_space < chunk_size) {
                ret = 0;
                goto get_out;
        }
        /* fifo has enough space left for next chunk! */
        bus_dmamap_sync(dmat,
                        fifo->f_dmamap,
                        fifo->f_pdc_wr_ndx,
                        chunk_size,
                        BUS_DMASYNC_PREREAD);
        bus_space_write_4(iot, ioh, offset + PDC_RNPR, fifo->f_buf_addr + fifo->f_pdc_wr_ndx);
        bus_space_write_4(iot, ioh, offset + PDC_RNCR, chunk_size);
        if ((fifo->f_pdc_wr_ndx += chunk_size) >= fifo->f_buf_size)
                fifo->f_pdc_wr_ndx = 0;
        fifo->f_pdc_space -= chunk_size;
get_out:
        /* now check if we need to re-synchronize last read chunk too */
        al = fifo->f_pdc_rd_ndx % chunk_size;
        if (al) {
                bus_dmamap_sync(dmat,
                                fifo->f_dmamap,
                                fifo->f_pdc_rd_ndx,
                                chunk_size - al,
                                BUS_DMASYNC_PREREAD);
        }
        return ret;
}

static __inline void AT91PDC_FIFO_POSTREAD(bus_space_tag_t iot,
                                            bus_space_handle_t ioh,
                                            bus_dma_tag_t dmat,
                                            uint offset,
                                            at91pdc_fifo_t *fifo)
{
        uint32_t        pdc_ptr = bus_space_read_4(iot, ioh, offset + PDC_RPR);
        int32_t         cc = pdc_ptr - fifo->f_buf_addr - fifo->f_pdc_rd_ndx;

        /* handle fifo wrapping: */
        if (cc < 0) {
                cc = fifo->f_buf_size - fifo->f_pdc_rd_ndx;
                if (cc > 0) {
                        bus_dmamap_sync(dmat, fifo->f_dmamap, fifo->f_pdc_rd_ndx, cc,
                                        BUS_DMASYNC_POSTREAD);
                        fifo->f_length += cc;
                        fifo->f_pdc_rd_ndx += cc;
                }
                fifo->f_pdc_rd_ndx = 0;
                cc = pdc_ptr - fifo->f_buf_addr;
        }

        if (cc > 0) {
                /* data has been received! */
                bus_dmamap_sync(dmat, fifo->f_dmamap, fifo->f_pdc_rd_ndx, cc,
                                BUS_DMASYNC_POSTREAD);
                fifo->f_length += cc;
                fifo->f_pdc_rd_ndx += cc;
        }
}

static __inline void *AT91PDC_FIFO_RDPTR(at91pdc_fifo_t *fifo, int *num_bytes)
{
        if (fifo->f_length <= 0) {
                return NULL;
        }
        int contig_bytes = fifo->f_buf_size - fifo->f_ndx;
        if (contig_bytes > fifo->f_length)
                contig_bytes = fifo->f_length;
        *num_bytes = contig_bytes;
        return (void*)((uintptr_t)fifo->f_buf + fifo->f_ndx);
}

static __inline void AT91PDC_FIFO_READ(at91pdc_fifo_t *fifo, int bytes_read)
{
        if (bytes_read > fifo->f_length)
                bytes_read = fifo->f_length;
        int contig_bytes = fifo->f_buf_size - fifo->f_ndx;
        fifo->f_length -= bytes_read;
        fifo->f_pdc_space += bytes_read;
        if (bytes_read < contig_bytes)
                fifo->f_ndx += bytes_read;
        else
                fifo->f_ndx = bytes_read - contig_bytes;
}

static __inline int AT91PDC_FIFO_PREWRITE(bus_space_tag_t iot,
                                           bus_space_handle_t ioh,
                                           bus_dma_tag_t dmat,
                                           uint offset,
                                           at91pdc_fifo_t *fifo,
                                           uint max_chunk_size)
{
        if (bus_space_read_4(iot, ioh, offset + PDC_TNCR) != 0)
                return 1;
        int len = fifo->f_buf_size - fifo->f_pdc_rd_ndx;
        int max_len = fifo->f_length - (fifo->f_buf_size - fifo->f_pdc_space);
        if (len > max_len)
                len = max_len;
        if (len > max_chunk_size)
                len = max_chunk_size;
        if (len > fifo->f_pdc_space)
                panic("%s: len %d > pdc_space (f_length=%d space=%d size=%d)",
                      __FUNCTION__, len, fifo->f_length, fifo->f_pdc_space, fifo->f_buf_size);
        if (len == 0)
                return 0;
        if (len < 0)
                panic("%s: len < 0 (f_length=%d space=%d size=%d)",
                      __FUNCTION__, fifo->f_length, fifo->f_pdc_space, fifo->f_buf_size);

        /* there's something to write */
        bus_dmamap_sync(dmat,
                        fifo->f_dmamap,
                        fifo->f_pdc_rd_ndx,
                        len,
                        BUS_DMASYNC_PREWRITE);
        bus_space_write_4(iot, ioh, offset + PDC_TNPR, fifo->f_buf_addr + fifo->f_pdc_rd_ndx);
        bus_space_write_4(iot, ioh, offset + PDC_TNCR, len);
        if ((fifo->f_pdc_rd_ndx += len) >= fifo->f_buf_size)
                fifo->f_pdc_rd_ndx = 0;
        fifo->f_pdc_space -= len;

        return 1;
}

static __inline void AT91PDC_FIFO_POSTWRITE(bus_space_tag_t iot,
                                             bus_space_handle_t ioh,
                                             bus_dma_tag_t dmat,
                                             uint offset,
                                             at91pdc_fifo_t *fifo)
{
        uint32_t        pdc_ptr = bus_space_read_4(iot, ioh, offset + PDC_TPR);
        int32_t         cc = pdc_ptr - fifo->f_buf_addr - fifo->f_pdc_wr_ndx;

        /* handle fifo wrapping: */
        if (cc < 0) {
                cc = fifo->f_buf_size - fifo->f_pdc_wr_ndx;
                if (cc > 0) {
                        bus_dmamap_sync(dmat, fifo->f_dmamap, fifo->f_pdc_wr_ndx, cc,
                                        BUS_DMASYNC_POSTWRITE);
                        fifo->f_length -= cc;
                        fifo->f_pdc_space += cc;
                }
                fifo->f_pdc_wr_ndx = 0;
                cc = pdc_ptr - fifo->f_buf_addr;
        }

        if (cc > 0) {
                /* data has been sent! */
                bus_dmamap_sync(dmat, fifo->f_dmamap, fifo->f_pdc_wr_ndx, cc,
                                BUS_DMASYNC_POSTWRITE);
                fifo->f_length -= cc;
                fifo->f_pdc_space += cc;
                fifo->f_pdc_wr_ndx += cc;
        }
}

static __inline void *AT91PDC_FIFO_WRPTR(at91pdc_fifo_t *fifo, int *max_bytes)
{
        int space = fifo->f_buf_size - fifo->f_length;
        if (space <= 0)
                return NULL;
        int contig_bytes = fifo->f_buf_size - fifo->f_ndx;
        if (contig_bytes > space)
                contig_bytes = space;
        *max_bytes = contig_bytes;
        return (void*)((uintptr_t)fifo->f_buf + fifo->f_ndx);
}

static __inline void AT91PDC_FIFO_WRITTEN(at91pdc_fifo_t *fifo, int bytes_written)
{
        if (bytes_written > (fifo->f_buf_size - fifo->f_length))
                bytes_written = (fifo->f_buf_size - fifo->f_length);
        int contig_bytes = fifo->f_buf_size - fifo->f_ndx;
        fifo->f_length += bytes_written;
        if (bytes_written < contig_bytes)
                fifo->f_ndx += bytes_written;
        else
                fifo->f_ndx = bytes_written - contig_bytes;
}

int at91pdc_alloc_fifo(bus_dma_tag_t dmat, at91pdc_fifo_t *fifo, int size, int flags);

#endif  // !_AT91PDCVAR_H_