summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/8390/mcf8390.c
blob: 38fcdcf7c4c7e301a522b56223c89eda865dfc40 (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
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
/*
 *  Support for ColdFire CPU based boards using a NS8390 Ethernet device.
 *
 *  Derived from the many other 8390 drivers.
 *
 *  (C) Copyright 2012,  Greg Ungerer <gerg@uclinux.org>
 *
 *  This file is subject to the terms and conditions of the GNU General Public
 *  License.  See the file COPYING in the main directory of the Linux
 *  distribution for more details.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/jiffies.h>
#include <linux/io.h>
#include <asm/mcf8390.h>

static const char version[] =
	"mcf8390.c: (15-06-2012) Greg Ungerer <gerg@uclinux.org>";

#define NE_CMD		0x00
#define NE_DATAPORT	0x10	/* NatSemi-defined port window offset */
#define NE_RESET	0x1f	/* Issue a read to reset ,a write to clear */
#define NE_EN0_ISR	0x07
#define NE_EN0_DCFG	0x0e
#define NE_EN0_RSARLO	0x08
#define NE_EN0_RSARHI	0x09
#define NE_EN0_RCNTLO	0x0a
#define NE_EN0_RXCR	0x0c
#define NE_EN0_TXCR	0x0d
#define NE_EN0_RCNTHI	0x0b
#define NE_EN0_IMR	0x0f

#define NESM_START_PG	0x40	/* First page of TX buffer */
#define NESM_STOP_PG	0x80	/* Last page +1 of RX ring */
static u32 mcf8390_msg_enable;

#ifdef NE2000_ODDOFFSET
/*
 * A lot of the ColdFire boards use a separate address region for odd offset
 * register addresses. The following functions convert and map as required.
 * Note that the data port accesses are treated a little differently, and
 * always accessed via the insX/outsX functions.
 */
static inline u32 NE_PTR(u32 addr)
{
	if (addr & 1)
		return addr - 1 + NE2000_ODDOFFSET;
	return addr;
}

static inline u32 NE_DATA_PTR(u32 addr)
{
	return addr;
}

void ei_outb(u32 val, u32 addr)
{
	NE2000_BYTE *rp;

	rp = (NE2000_BYTE *) NE_PTR(addr);
	*rp = RSWAP(val);
}

#define	ei_inb	ei_inb
u8 ei_inb(u32 addr)
{
	NE2000_BYTE *rp, val;

	rp = (NE2000_BYTE *) NE_PTR(addr);
	val = *rp;
	return (u8) (RSWAP(val) & 0xff);
}

void ei_insb(u32 addr, void *vbuf, int len)
{
	NE2000_BYTE *rp, val;
	u8 *buf;

	buf = (u8 *) vbuf;
	rp = (NE2000_BYTE *) NE_DATA_PTR(addr);
	for (; (len > 0); len--) {
		val = *rp;
		*buf++ = RSWAP(val);
	}
}

void ei_insw(u32 addr, void *vbuf, int len)
{
	volatile u16 *rp;
	u16 w, *buf;

	buf = (u16 *) vbuf;
	rp = (volatile u16 *) NE_DATA_PTR(addr);
	for (; (len > 0); len--) {
		w = *rp;
		*buf++ = BSWAP(w);
	}
}

void ei_outsb(u32 addr, const void *vbuf, int len)
{
	NE2000_BYTE *rp, val;
	u8 *buf;

	buf = (u8 *) vbuf;
	rp = (NE2000_BYTE *) NE_DATA_PTR(addr);
	for (; (len > 0); len--) {
		val = *buf++;
		*rp = RSWAP(val);
	}
}

void ei_outsw(u32 addr, const void *vbuf, int len)
{
	volatile u16 *rp;
	u16 w, *buf;

	buf = (u16 *) vbuf;
	rp = (volatile u16 *) NE_DATA_PTR(addr);
	for (; (len > 0); len--) {
		w = *buf++;
		*rp = BSWAP(w);
	}
}

#else /* !NE2000_ODDOFFSET */

#define	ei_inb		inb
#define	ei_outb		outb
#define	ei_insb		insb
#define	ei_insw		insw
#define	ei_outsb	outsb
#define	ei_outsw	outsw

#endif /* !NE2000_ODDOFFSET */

#define	ei_inb_p	ei_inb
#define	ei_outb_p	ei_outb

#include "lib8390.c"

/*
 * Hard reset the card. This used to pause for the same period that a
 * 8390 reset command required, but that shouldn't be necessary.
 */
static void mcf8390_reset_8390(struct net_device *dev)
{
	unsigned long reset_start_time = jiffies;
	u32 addr = dev->base_addr;
	struct ei_device *ei_local = netdev_priv(dev);

	netif_dbg(ei_local, hw, dev, "resetting the 8390 t=%ld...\n", jiffies);

	ei_outb(ei_inb(addr + NE_RESET), addr + NE_RESET);

	ei_status.txing = 0;
	ei_status.dmaing = 0;

	/* This check _should_not_ be necessary, omit eventually. */
	while ((ei_inb(addr + NE_EN0_ISR) & ENISR_RESET) == 0) {
		if (time_after(jiffies, reset_start_time + 2 * HZ / 100)) {
			netdev_warn(dev, "%s: did not complete\n", __func__);
			break;
		}
	}

	ei_outb(ENISR_RESET, addr + NE_EN0_ISR);
}

/*
 * This *shouldn't* happen.
 * If it does, it's the last thing you'll see
 */
static void mcf8390_dmaing_err(const char *func, struct net_device *dev,
			       struct ei_device *ei_local)
{
	netdev_err(dev, "%s: DMAing conflict [DMAstat:%d][irqlock:%d]\n",
		func, ei_local->dmaing, ei_local->irqlock);
}

/*
 * Grab the 8390 specific header. Similar to the block_input routine, but
 * we don't need to be concerned with ring wrap as the header will be at
 * the start of a page, so we optimize accordingly.
 */
static void mcf8390_get_8390_hdr(struct net_device *dev,
				 struct e8390_pkt_hdr *hdr, int ring_page)
{
	struct ei_device *ei_local = netdev_priv(dev);
	u32 addr = dev->base_addr;

	if (ei_local->dmaing) {
		mcf8390_dmaing_err(__func__, dev, ei_local);
		return;
	}

	ei_local->dmaing |= 0x01;
	ei_outb(E8390_NODMA + E8390_PAGE0 + E8390_START, addr + NE_CMD);
	ei_outb(ENISR_RDC, addr + NE_EN0_ISR);
	ei_outb(sizeof(struct e8390_pkt_hdr), addr + NE_EN0_RCNTLO);
	ei_outb(0, addr + NE_EN0_RCNTHI);
	ei_outb(0, addr + NE_EN0_RSARLO);		/* On page boundary */
	ei_outb(ring_page, addr + NE_EN0_RSARHI);
	ei_outb(E8390_RREAD + E8390_START, addr + NE_CMD);

	ei_insw(addr + NE_DATAPORT, hdr, sizeof(struct e8390_pkt_hdr) >> 1);

	outb(ENISR_RDC, addr + NE_EN0_ISR);	/* Ack intr */
	ei_local->dmaing &= ~0x01;

	hdr->count = cpu_to_le16(hdr->count);
}

/*
 * Block input and output, similar to the Crynwr packet driver.
 * If you are porting to a new ethercard, look at the packet driver source
 * for hints. The NEx000 doesn't share the on-board packet memory --
 * you have to put the packet out through the "remote DMA" dataport
 * using z_writeb.
 */
static void mcf8390_block_input(struct net_device *dev, int count,
				struct sk_buff *skb, int ring_offset)
{
	struct ei_device *ei_local = netdev_priv(dev);
	u32 addr = dev->base_addr;
	char *buf = skb->data;

	if (ei_local->dmaing) {
		mcf8390_dmaing_err(__func__, dev, ei_local);
		return;
	}

	ei_local->dmaing |= 0x01;
	ei_outb(E8390_NODMA + E8390_PAGE0 + E8390_START, addr + NE_CMD);
	ei_outb(ENISR_RDC, addr + NE_EN0_ISR);
	ei_outb(count & 0xff, addr + NE_EN0_RCNTLO);
	ei_outb(count >> 8, addr + NE_EN0_RCNTHI);
	ei_outb(ring_offset & 0xff, addr + NE_EN0_RSARLO);
	ei_outb(ring_offset >> 8, addr + NE_EN0_RSARHI);
	ei_outb(E8390_RREAD + E8390_START, addr + NE_CMD);

	ei_insw(addr + NE_DATAPORT, buf, count >> 1);
	if (count & 1)
		buf[count - 1] = ei_inb(addr + NE_DATAPORT);

	ei_outb(ENISR_RDC, addr + NE_EN0_ISR);	/* Ack intr */
	ei_local->dmaing &= ~0x01;
}

static void mcf8390_block_output(struct net_device *dev, int count,
				 const unsigned char *buf,
				 const int start_page)
{
	struct ei_device *ei_local = netdev_priv(dev);
	u32 addr = dev->base_addr;
	unsigned long dma_start;

	/* Make sure we transfer all bytes if 16bit IO writes */
	if (count & 0x1)
		count++;

	if (ei_local->dmaing) {
		mcf8390_dmaing_err(__func__, dev, ei_local);
		return;
	}

	ei_local->dmaing |= 0x01;
	/* We should already be in page 0, but to be safe... */
	ei_outb(E8390_PAGE0 + E8390_START + E8390_NODMA, addr + NE_CMD);

	ei_outb(ENISR_RDC, addr + NE_EN0_ISR);

	/* Now the normal output. */
	ei_outb(count & 0xff, addr + NE_EN0_RCNTLO);
	ei_outb(count >> 8, addr + NE_EN0_RCNTHI);
	ei_outb(0x00, addr + NE_EN0_RSARLO);
	ei_outb(start_page, addr + NE_EN0_RSARHI);
	ei_outb(E8390_RWRITE + E8390_START, addr + NE_CMD);

	ei_outsw(addr + NE_DATAPORT, buf, count >> 1);

	dma_start = jiffies;
	while ((ei_inb(addr + NE_EN0_ISR) & ENISR_RDC) == 0) {
		if (time_after(jiffies, dma_start + 2 * HZ / 100)) { /* 20ms */
			netdev_warn(dev, "timeout waiting for Tx RDC\n");
			mcf8390_reset_8390(dev);
			__NS8390_init(dev, 1);
			break;
		}
	}

	ei_outb(ENISR_RDC, addr + NE_EN0_ISR);	/* Ack intr */
	ei_local->dmaing &= ~0x01;
}

static const struct net_device_ops mcf8390_netdev_ops = {
	.ndo_open		= __ei_open,
	.ndo_stop		= __ei_close,
	.ndo_start_xmit		= __ei_start_xmit,
	.ndo_tx_timeout		= __ei_tx_timeout,
	.ndo_get_stats		= __ei_get_stats,
	.ndo_set_rx_mode	= __ei_set_multicast_list,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_set_mac_address	= eth_mac_addr,
	.ndo_change_mtu		= eth_change_mtu,
#ifdef CONFIG_NET_POLL_CONTROLLER
	.ndo_poll_controller	= __ei_poll,
#endif
};

static int mcf8390_init(struct net_device *dev)
{
	static u32 offsets[] = {
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
		0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
	};
	struct ei_device *ei_local = netdev_priv(dev);
	unsigned char SA_prom[32];
	u32 addr = dev->base_addr;
	int start_page, stop_page;
	int i, ret;

	mcf8390_reset_8390(dev);

	/*
	 * Read the 16 bytes of station address PROM.
	 * We must first initialize registers,
	 * similar to NS8390_init(eifdev, 0).
	 * We can't reliably read the SAPROM address without this.
	 * (I learned the hard way!).
	 */
	{
		static const struct {
			u32 value;
			u32 offset;
		} program_seq[] = {
			{E8390_NODMA + E8390_PAGE0 + E8390_STOP, NE_CMD},
						/* Select page 0 */
			{0x48,	NE_EN0_DCFG},	/* 0x48: Set byte-wide access */
			{0x00,	NE_EN0_RCNTLO},	/* Clear the count regs */
			{0x00,	NE_EN0_RCNTHI},
			{0x00,	NE_EN0_IMR},	/* Mask completion irq */
			{0xFF,	NE_EN0_ISR},
			{E8390_RXOFF, NE_EN0_RXCR}, /* 0x20 Set to monitor */
			{E8390_TXOFF, NE_EN0_TXCR}, /* 0x02 and loopback mode */
			{32,	NE_EN0_RCNTLO},
			{0x00,	NE_EN0_RCNTHI},
			{0x00,	NE_EN0_RSARLO},	/* DMA starting at 0x0000 */
			{0x00,	NE_EN0_RSARHI},
			{E8390_RREAD + E8390_START, NE_CMD},
		};
		for (i = 0; i < ARRAY_SIZE(program_seq); i++) {
			ei_outb(program_seq[i].value,
				 addr + program_seq[i].offset);
		}
	}

	for (i = 0; i < 16; i++) {
		SA_prom[i] = ei_inb(addr + NE_DATAPORT);
		ei_inb(addr + NE_DATAPORT);
	}

	/* We must set the 8390 for word mode. */
	ei_outb(0x49, addr + NE_EN0_DCFG);
	start_page = NESM_START_PG;
	stop_page = NESM_STOP_PG;

	/* Install the Interrupt handler */
	ret = request_irq(dev->irq, __ei_interrupt, 0, dev->name, dev);
	if (ret)
		return ret;

	for (i = 0; i < ETH_ALEN; i++)
		dev->dev_addr[i] = SA_prom[i];

	netdev_dbg(dev, "Found ethernet address: %pM\n", dev->dev_addr);

	ei_local->name = "mcf8390";
	ei_local->tx_start_page = start_page;
	ei_local->stop_page = stop_page;
	ei_local->word16 = 1;
	ei_local->rx_start_page = start_page + TX_PAGES;
	ei_local->reset_8390 = mcf8390_reset_8390;
	ei_local->block_input = mcf8390_block_input;
	ei_local->block_output = mcf8390_block_output;
	ei_local->get_8390_hdr = mcf8390_get_8390_hdr;
	ei_local->reg_offset = offsets;

	dev->netdev_ops = &mcf8390_netdev_ops;
	__NS8390_init(dev, 0);
	ret = register_netdev(dev);
	if (ret) {
		free_irq(dev->irq, dev);
		return ret;
	}

	netdev_info(dev, "addr=0x%08x irq=%d, Ethernet Address %pM\n",
		addr, dev->irq, dev->dev_addr);
	return 0;
}

static int mcf8390_probe(struct platform_device *pdev)
{
	struct net_device *dev;
	struct ei_device *ei_local;
	struct resource *mem, *irq;
	resource_size_t msize;
	int ret;

	irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (irq == NULL) {
		dev_err(&pdev->dev, "no IRQ specified?\n");
		return -ENXIO;
	}

	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (mem == NULL) {
		dev_err(&pdev->dev, "no memory address specified?\n");
		return -ENXIO;
	}
	msize = resource_size(mem);
	if (!request_mem_region(mem->start, msize, pdev->name))
		return -EBUSY;

	dev = ____alloc_ei_netdev(0);
	if (dev == NULL) {
		release_mem_region(mem->start, msize);
		return -ENOMEM;
	}

	SET_NETDEV_DEV(dev, &pdev->dev);
	platform_set_drvdata(pdev, dev);
	ei_local = netdev_priv(dev);
	ei_local->msg_enable = mcf8390_msg_enable;

	dev->irq = irq->start;
	dev->base_addr = mem->start;

	ret = mcf8390_init(dev);
	if (ret) {
		release_mem_region(mem->start, msize);
		free_netdev(dev);
		return ret;
	}
	return 0;
}

static int mcf8390_remove(struct platform_device *pdev)
{
	struct net_device *dev = platform_get_drvdata(pdev);
	struct resource *mem;

	unregister_netdev(dev);
	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (mem)
		release_mem_region(mem->start, resource_size(mem));
	free_netdev(dev);
	return 0;
}

static struct platform_driver mcf8390_drv = {
	.driver = {
		.name	= "mcf8390",
		.owner	= THIS_MODULE,
	},
	.probe		= mcf8390_probe,
	.remove		= mcf8390_remove,
};

module_platform_driver(mcf8390_drv);

MODULE_DESCRIPTION("MCF8390 ColdFire NS8390 driver");
MODULE_AUTHOR("Greg Ungerer <gerg@uclinux.org>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mcf8390");
OpenPOWER on IntegriCloud