aboutsummaryrefslogtreecommitdiffstats
path: root/arch/sh64/kernel/dma.c
blob: 32c6f0549bf1f1523a749d1036e9929526fbef63 (plain) (blame)
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/*
 * arch/sh64/kernel/dma.c
 *
 * DMA routines for the SH-5 DMAC.
 *
 * Copyright (C) 2003  Paul Mundt
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <asm/hardware.h>
#include <asm/dma.h>
#include <asm/signal.h>
#include <asm/errno.h>
#include <asm/io.h>

typedef struct {
	unsigned long dev_addr;
	unsigned long mem_addr;

	unsigned int mode;
	unsigned int count;
} dma_info_t;

static dma_info_t dma_info[MAX_DMA_CHANNELS];
static DEFINE_SPINLOCK(dma_spin_lock);

/* arch/sh64/kernel/irq_intc.c */
extern void make_intc_irq(unsigned int irq);

/* DMAC Interrupts */
#define DMA_IRQ_DMTE0	18
#define DMA_IRQ_DERR	22

#define DMAC_COMMON_BASE	(dmac_base + 0x08)
#define DMAC_SAR_BASE		(dmac_base + 0x10)
#define DMAC_DAR_BASE		(dmac_base + 0x18)
#define DMAC_COUNT_BASE		(dmac_base + 0x20)
#define DMAC_CTRL_BASE		(dmac_base + 0x28)
#define DMAC_STATUS_BASE	(dmac_base + 0x30)

#define DMAC_SAR(n)	(DMAC_SAR_BASE    + ((n) * 0x28))
#define DMAC_DAR(n)	(DMAC_DAR_BASE    + ((n) * 0x28))
#define DMAC_COUNT(n)	(DMAC_COUNT_BASE  + ((n) * 0x28))
#define DMAC_CTRL(n)	(DMAC_CTRL_BASE   + ((n) * 0x28))
#define DMAC_STATUS(n)	(DMAC_STATUS_BASE + ((n) * 0x28))

/* DMAC.COMMON Bit Definitions */
#define DMAC_COMMON_PR	0x00000001	/* Priority */
					/* Bits 1-2 Reserved */
#define DMAC_COMMON_ME	0x00000008	/* Master Enable */
#define DMAC_COMMON_NMI	0x00000010	/* NMI Flag */
					/* Bits 5-6 Reserved */
#define DMAC_COMMON_ER	0x00000780	/* Error Response */
#define DMAC_COMMON_AAE	0x00007800	/* Address Alignment Error */
					/* Bits 15-63 Reserved */

/* DMAC.SAR Bit Definitions */
#define DMAC_SAR_ADDR	0xffffffff	/* Source Address */

/* DMAC.DAR Bit Definitions */
#define DMAC_DAR_ADDR	0xffffffff	/* Destination Address */

/* DMAC.COUNT Bit Definitions */
#define DMAC_COUNT_CNT	0xffffffff	/* Transfer Count */

/* DMAC.CTRL Bit Definitions */
#define DMAC_CTRL_TS	0x00000007	/* Transfer Size */
#define DMAC_CTRL_SI	0x00000018	/* Source Increment */
#define DMAC_CTRL_DI	0x00000060	/* Destination Increment */
#define DMAC_CTRL_RS	0x00000780	/* Resource Select */
#define DMAC_CTRL_IE	0x00000800	/* Interrupt Enable */
#define DMAC_CTRL_TE	0x00001000	/* Transfer Enable */
					/* Bits 15-63 Reserved */

/* DMAC.STATUS Bit Definitions */
#define DMAC_STATUS_TE	0x00000001	/* Transfer End */
#define DMAC_STATUS_AAE	0x00000002	/* Address Alignment Error */
					/* Bits 2-63 Reserved */

static unsigned long dmac_base;

void set_dma_count(unsigned int chan, unsigned int count);
void set_dma_addr(unsigned int chan, unsigned int addr);

static irqreturn_t dma_mte(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned int chan = irq - DMA_IRQ_DMTE0;
	dma_info_t *info = dma_info + chan;
	u64 status;

	if (info->mode & DMA_MODE_WRITE) {
		sh64_out64(info->mem_addr & DMAC_SAR_ADDR, DMAC_SAR(chan));
	} else {
		sh64_out64(info->mem_addr & DMAC_DAR_ADDR, DMAC_DAR(chan));
	}

	set_dma_count(chan, info->count);

	/* Clear the TE bit */
	status = sh64_in64(DMAC_STATUS(chan));
	status &= ~DMAC_STATUS_TE;
	sh64_out64(status, DMAC_STATUS(chan));

	return IRQ_HANDLED;
}

static struct irqaction irq_dmte = {
	.handler	= dma_mte,
	.flags		= IRQF_DISABLED,
	.name		= "DMA MTE",
};

static irqreturn_t dma_err(int irq, void *dev_id, struct pt_regs *regs)
{
	u64 tmp;
	u8 chan;

	printk(KERN_NOTICE "DMAC: Got a DMA Error!\n");

	tmp = sh64_in64(DMAC_COMMON_BASE);

	/* Check for the type of error */
	if ((chan = tmp & DMAC_COMMON_AAE)) {
		/* It's an address alignment error.. */
		printk(KERN_NOTICE "DMAC: Alignment error on channel %d, ", chan);

		printk(KERN_NOTICE "SAR: 0x%08llx, DAR: 0x%08llx, COUNT: %lld\n",
		       (sh64_in64(DMAC_SAR(chan)) & DMAC_SAR_ADDR),
		       (sh64_in64(DMAC_DAR(chan)) & DMAC_DAR_ADDR),
		       (sh64_in64(DMAC_COUNT(chan)) & DMAC_COUNT_CNT));

	} else if ((chan = tmp & DMAC_COMMON_ER)) {
		/* Something else went wrong.. */
		printk(KERN_NOTICE "DMAC: Error on channel %d\n", chan);
	}

	/* Reset the ME bit to clear the interrupt */
	tmp |= DMAC_COMMON_ME;
	sh64_out64(tmp, DMAC_COMMON_BASE);

	return IRQ_HANDLED;
}

static struct irqaction irq_derr = {
	.handler	= dma_err,
	.flags		= IRQF_DISABLED,
	.name		= "DMA Error",
};

static inline unsigned long calc_xmit_shift(unsigned int chan)
{
	return sh64_in64(DMAC_CTRL(chan)) & 0x03;
}

void setup_dma(unsigned int chan, dma_info_t *info)
{
	unsigned int irq = DMA_IRQ_DMTE0 + chan;
	dma_info_t *dma = dma_info + chan;

	make_intc_irq(irq);
	setup_irq(irq, &irq_dmte);
	dma = info;
}

void enable_dma(unsigned int chan)
{
	u64 ctrl;

	ctrl = sh64_in64(DMAC_CTRL(chan));
	ctrl |= DMAC_CTRL_TE;
	sh64_out64(ctrl, DMAC_CTRL(chan));
}

void disable_dma(unsigned int chan)
{
	u64 ctrl;

	ctrl = sh64_in64(DMAC_CTRL(chan));
	ctrl &= ~DMAC_CTRL_TE;
	sh64_out64(ctrl, DMAC_CTRL(chan));
}

void set_dma_mode(unsigned int chan, char mode)
{
	dma_info_t *info = dma_info + chan;

	info->mode = mode;

	set_dma_addr(chan, info->mem_addr);
	set_dma_count(chan, info->count);
}

void set_dma_addr(unsigned int chan, unsigned int addr)
{
	dma_info_t *info = dma_info + chan;
	unsigned long sar, dar;

	info->mem_addr = addr;
	sar = (info->mode & DMA_MODE_WRITE) ? info->mem_addr : info->dev_addr;
	dar = (info->mode & DMA_MODE_WRITE) ? info->dev_addr : info->mem_addr;

	sh64_out64(sar & DMAC_SAR_ADDR, DMAC_SAR(chan));
	sh64_out64(dar & DMAC_SAR_ADDR, DMAC_DAR(chan));
}

void set_dma_count(unsigned int chan, unsigned int count)
{
	dma_info_t *info = dma_info + chan;
	u64 tmp;

	info->count = count;

	tmp = (info->count >> calc_xmit_shift(chan)) & DMAC_COUNT_CNT;

	sh64_out64(tmp, DMAC_COUNT(chan));
}

unsigned long claim_dma_lock(void)
{
	unsigned long flags;

	spin_lock_irqsave(&dma_spin_lock, flags);

	return flags;
}

void release_dma_lock(unsigned long flags)
{
	spin_unlock_irqrestore(&dma_spin_lock, flags);
}

int get_dma_residue(unsigned int chan)
{
	return sh64_in64(DMAC_COUNT(chan) << calc_xmit_shift(chan));
}

int __init init_dma(void)
{
	struct vcr_info vcr;
	u64 tmp;

	/* Remap the DMAC */
	dmac_base = onchip_remap(PHYS_DMAC_BLOCK, 1024, "DMAC");
	if (!dmac_base) {
		printk(KERN_ERR "Unable to remap DMAC\n");
		return -ENOMEM;
	}

	/* Report DMAC.VCR Info */
	vcr = sh64_get_vcr_info(dmac_base);
	printk("DMAC: Module ID: 0x%04x, Module version: 0x%04x\n",
	       vcr.mod_id, vcr.mod_vers);

	/* Set the ME bit */
	tmp = sh64_in64(DMAC_COMMON_BASE);
	tmp |= DMAC_COMMON_ME;
	sh64_out64(tmp, DMAC_COMMON_BASE);

	/* Enable the DMAC Error Interrupt */
	make_intc_irq(DMA_IRQ_DERR);
	setup_irq(DMA_IRQ_DERR, &irq_derr);

	return 0;
}

static void __exit exit_dma(void)
{
	onchip_unmap(dmac_base);
	free_irq(DMA_IRQ_DERR, 0);
}

module_init(init_dma);
module_exit(exit_dma);

MODULE_AUTHOR("Paul Mundt");
MODULE_DESCRIPTION("DMA API for SH-5 DMAC");
MODULE_LICENSE("GPL");

EXPORT_SYMBOL(setup_dma);
EXPORT_SYMBOL(claim_dma_lock);
EXPORT_SYMBOL(release_dma_lock);
EXPORT_SYMBOL(enable_dma);
EXPORT_SYMBOL(disable_dma);
EXPORT_SYMBOL(set_dma_mode);
EXPORT_SYMBOL(set_dma_addr);
EXPORT_SYMBOL(set_dma_count);
EXPORT_SYMBOL(get_dma_residue);