aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/fwserial/dma_fifo.h
blob: a113fe1e6f198323194595ebb6f06ab4d691a768 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
 * DMA-able FIFO interface
 *
 * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
 *
 * 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 program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifndef _DMA_FIFO_H_
#define _DMA_FIFO_H_

/**
 * The design basis for the DMA FIFO is to provide an output side that
 * complies with the streaming DMA API design that can be DMA'd from directly
 * (without additional copying), coupled with an input side that maintains a
 * logically consistent 'apparent' size (ie, bytes in + bytes avail is static
 * for the lifetime of the FIFO).
 *
 * DMA output transactions originate on a cache line boundary and can be
 * variably-sized. DMA output transactions can be retired out-of-order but
 * the FIFO will only advance the output in the original input sequence.
 * This means the FIFO will eventually stall if a transaction is never retired.
 *
 * Chunking the output side into cache line multiples means that some FIFO
 * memory is unused. For example, if all the avail input has been pended out,
 * then the in and out markers are re-aligned to the next cache line.
 * The maximum possible waste is
 *     (cache line alignment - 1) * (max outstanding dma transactions)
 * This potential waste requires additional hidden capacity within the FIFO
 * to be able to accept input while the 'apparent' size has not been reached.
 *
 * Additional cache lines (ie, guard area) are used to minimize DMA
 * fragmentation when wrapping at the end of the FIFO. Input is allowed into the
 * guard area, but the in and out FIFO markers are wrapped when DMA is pended.
 */

#define DMA_FIFO_GUARD 3   /* # of cache lines to reserve for the guard area */

struct dma_fifo {
	unsigned	 in;
	unsigned	 out;		/* updated when dma is pended         */
	unsigned	 done;		/* updated upon dma completion        */
	struct {
		unsigned corrupt:1;
	};
	int		 size;		/* 'apparent' size of fifo	      */
	int		 guard;		/* ofs of guard area		      */
	int		 capacity;	/* size + reserved                    */
	int		 avail;		/* # of unused bytes in fifo          */
	unsigned	 align;		/* must be power of 2                 */
	int		 tx_limit;	/* max # of bytes per dma transaction */
	int		 open_limit;	/* max # of outstanding allowed       */
	int		 open;		/* # of outstanding dma transactions  */
	struct list_head pending;	/* fifo markers for outstanding dma   */
	void		 *data;
};

struct dma_pending {
	struct list_head link;
	void		 *data;
	unsigned	 len;
	unsigned         next;
	unsigned         out;
};

static inline void dp_mark_completed(struct dma_pending *dp)
{
	dp->data += 1;
}

static inline bool dp_is_completed(struct dma_pending *dp)
{
	return (unsigned long)dp->data & 1UL;
}

extern void dma_fifo_init(struct dma_fifo *fifo);
extern int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
			  int tx_limit, int open_limit, gfp_t gfp_mask);
extern void dma_fifo_free(struct dma_fifo *fifo);
extern void dma_fifo_reset(struct dma_fifo *fifo);
extern int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n);
extern int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended);
extern int dma_fifo_out_complete(struct dma_fifo *fifo,
				 struct dma_pending *complete);

/* returns the # of used bytes in the fifo */
static inline int dma_fifo_level(struct dma_fifo *fifo)
{
	return fifo->size - fifo->avail;
}

/* returns the # of bytes ready for output in the fifo */
static inline int dma_fifo_out_level(struct dma_fifo *fifo)
{
	return fifo->in - fifo->out;
}

/* returns the # of unused bytes in the fifo */
static inline int dma_fifo_avail(struct dma_fifo *fifo)
{
	return fifo->avail;
}

/* returns true if fifo has max # of outstanding dmas */
static inline bool dma_fifo_busy(struct dma_fifo *fifo)
{
	return fifo->open == fifo->open_limit;
}

/* changes the max size of dma returned from dma_fifo_out_pend() */
static inline int dma_fifo_change_tx_limit(struct dma_fifo *fifo, int tx_limit)
{
	tx_limit = round_down(tx_limit, fifo->align);
	fifo->tx_limit = max_t(int, tx_limit, fifo->align);
	return 0;
}

#endif /* _DMA_FIFO_H_ */