summaryrefslogtreecommitdiff
path: root/chip/mec1322/dma.c
blob: a6c6fed5adb37466a5a089387ec9d64c47bf2327 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* Copyright 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "common.h"
#include "console.h"
#include "dma.h"
#include "hooks.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_DMA, outstr)
#define CPRINTS(format, args...) cprints(CC_DMA, format, ## args)

mec1322_dma_chan_t *dma_get_channel(enum dma_channel channel)
{
	mec1322_dma_regs_t *dma = MEC1322_DMA_REGS;

	return &dma->chan[channel];
}

void dma_disable(enum dma_channel channel)
{
	mec1322_dma_chan_t *chan = dma_get_channel(channel);

	if (chan->ctrl & BIT(0))
		chan->ctrl &= ~BIT(0);

	if (chan->act == 1)
		chan->act = 0;
}

void dma_disable_all(void)
{
	int ch;
	mec1322_dma_regs_t *dma;

	for (ch = 0; ch < MEC1322_DMAC_COUNT; ch++) {
		mec1322_dma_chan_t *chan = dma_get_channel(ch);
		/* Abort any current transfer. */
		chan->ctrl |= BIT(25);
		/* Disable the channel. */
		chan->ctrl &= ~BIT(0);
		chan->act = 0;
	}

	/* Soft-reset the block. */
	dma = MEC1322_DMA_REGS;
	dma->ctrl |= 0x2;
}

/**
 * Prepare a channel for use and start it
 *
 * @param chan		Channel to read
 * @param count		Number of bytes to transfer
 * @param periph	Pointer to peripheral data register
 * @param memory	Pointer to memory address for receive/transmit
 * @param flags		DMA flags for the control register, normally:
 *				MEC1322_DMA_INC_MEM | MEC1322_DMA_TO_DEV for tx
 *				MEC1322_DMA_INC_MEM for rx
 */
static void prepare_channel(mec1322_dma_chan_t *chan, unsigned count,
		void *periph, void *memory, unsigned flags)
{
	int xfer_size = (flags >> 20) & 0x7;

	if (chan->ctrl & BIT(0))
		chan->ctrl &= ~BIT(0);

	chan->act |= 0x1;
	chan->dev = (uint32_t)periph;
	chan->mem_start = MEC1322_RAM_ALIAS((uint32_t)memory);
	chan->mem_end = MEC1322_RAM_ALIAS((uint32_t)memory) + xfer_size * count;
	chan->ctrl = flags;
}

void dma_go(mec1322_dma_chan_t *chan)
{
	/* Flush data in write buffer so that DMA can get the latest data */
	asm volatile("dsb;");

	/* Fire it up */
	chan->ctrl |= MEC1322_DMA_RUN;
}

void dma_prepare_tx(const struct dma_option *option, unsigned count,
		    const void *memory)
{
	mec1322_dma_chan_t *chan = dma_get_channel(option->channel);

	/*
	 * Cast away const for memory pointer; this is ok because we know
	 * we're preparing the channel for transmit.
	 */
	prepare_channel(chan, count, option->periph, (void *)memory,
			MEC1322_DMA_INC_MEM | MEC1322_DMA_TO_DEV |
			MEC1322_DMA_DEV(option->channel) | option->flags);
}

void dma_start_rx(const struct dma_option *option, unsigned count,
		  void *memory)
{
	mec1322_dma_chan_t *chan;

	chan = dma_get_channel(option->channel);

	prepare_channel(chan, count, option->periph, memory,
			MEC1322_DMA_INC_MEM | MEC1322_DMA_DEV(option->channel) |
			option->flags);
	dma_go(chan);
}

int dma_bytes_done(mec1322_dma_chan_t *chan, int orig_count)
{
	int xfer_size = (chan->ctrl >> 20) & 0x7;

	return orig_count - (chan->mem_end - chan->mem_start) / xfer_size;
}

bool dma_is_enabled(mec1322_dma_chan_t *chan)
{
	return (chan->ctrl & MEC1322_DMA_RUN);
}

void dma_init(void)
{
	mec1322_dma_regs_t *dma = MEC1322_DMA_REGS;
	dma->ctrl |= 0x1;
}

int dma_wait(enum dma_channel channel)
{
	mec1322_dma_chan_t *chan = dma_get_channel(channel);
	timestamp_t deadline;

	if (chan->act == 0)
		return EC_SUCCESS;

	deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
	while (!(chan->int_status & 0x4)) {
		if (deadline.val <= get_time().val)
			return EC_ERROR_TIMEOUT;

		udelay(DMA_POLLING_INTERVAL_US);
	}
	return EC_SUCCESS;
}

void dma_clear_isr(enum dma_channel channel)
{
	mec1322_dma_chan_t *chan = dma_get_channel(channel);

	chan->int_status |= 0x4;
}