summaryrefslogtreecommitdiffstats
path: root/drivers/misc/arm-charlcd.c
blob: c65b5ea5d5ef44c90713bae61e9a0c461e3be93e (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
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
/*
 * Driver for the on-board character LCD found on some ARM reference boards
 * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it
 * http://en.wikipedia.org/wiki/HD44780_Character_LCD
 * Currently it will just display the text "ARM Linux" and the linux version
 *
 * License terms: GNU General Public License (GPL) version 2
 * Author: Linus Walleij <triad@df.lth.se>
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <generated/utsrelease.h>

#define DRIVERNAME "arm-charlcd"
#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000))

/* Offsets to registers */
#define CHAR_COM	0x00U
#define CHAR_DAT	0x04U
#define CHAR_RD		0x08U
#define CHAR_RAW	0x0CU
#define CHAR_MASK	0x10U
#define CHAR_STAT	0x14U

#define CHAR_RAW_CLEAR	0x00000000U
#define CHAR_RAW_VALID	0x00000100U

/* Hitachi HD44780 display commands */
#define HD_CLEAR			0x01U
#define HD_HOME				0x02U
#define HD_ENTRYMODE			0x04U
#define HD_ENTRYMODE_INCREMENT		0x02U
#define HD_ENTRYMODE_SHIFT		0x01U
#define HD_DISPCTRL			0x08U
#define HD_DISPCTRL_ON			0x04U
#define HD_DISPCTRL_CURSOR_ON		0x02U
#define HD_DISPCTRL_CURSOR_BLINK	0x01U
#define HD_CRSR_SHIFT			0x10U
#define HD_CRSR_SHIFT_DISPLAY		0x08U
#define HD_CRSR_SHIFT_DISPLAY_RIGHT	0x04U
#define HD_FUNCSET			0x20U
#define HD_FUNCSET_8BIT			0x10U
#define HD_FUNCSET_2_LINES		0x08U
#define HD_FUNCSET_FONT_5X10		0x04U
#define HD_SET_CGRAM			0x40U
#define HD_SET_DDRAM			0x80U
#define HD_BUSY_FLAG			0x80U

/**
 * @dev: a pointer back to containing device
 * @phybase: the offset to the controller in physical memory
 * @physize: the size of the physical page
 * @virtbase: the offset to the controller in virtual memory
 * @irq: reserved interrupt number
 * @complete: completion structure for the last LCD command
 */
struct charlcd {
	struct device *dev;
	u32 phybase;
	u32 physize;
	void __iomem *virtbase;
	int irq;
	struct completion complete;
	struct delayed_work init_work;
};

static irqreturn_t charlcd_interrupt(int irq, void *data)
{
	struct charlcd *lcd = data;
	u8 status;

	status = readl(lcd->virtbase + CHAR_STAT) & 0x01;
	/* Clear IRQ */
	writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
	if (status)
		complete(&lcd->complete);
	else
		dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status);
	return IRQ_HANDLED;
}


static void charlcd_wait_complete_irq(struct charlcd *lcd)
{
	int ret;

	ret = wait_for_completion_interruptible_timeout(&lcd->complete,
							CHARLCD_TIMEOUT);
	/* Disable IRQ after completion */
	writel(0x00, lcd->virtbase + CHAR_MASK);

	if (ret < 0) {
		dev_err(lcd->dev,
			"wait_for_completion_interruptible_timeout() "
			"returned %d waiting for ready\n", ret);
		return;
	}

	if (ret == 0) {
		dev_err(lcd->dev, "charlcd controller timed out "
			"waiting for ready\n");
		return;
	}
}

static u8 charlcd_4bit_read_char(struct charlcd *lcd)
{
	u8 data;
	u32 val;
	int i;

	/* If we can, use an IRQ to wait for the data, else poll */
	if (lcd->irq >= 0)
		charlcd_wait_complete_irq(lcd);
	else {
		i = 0;
		val = 0;
		while (!(val & CHAR_RAW_VALID) && i < 10) {
			udelay(100);
			val = readl(lcd->virtbase + CHAR_RAW);
			i++;
		}

		writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
	}
	msleep(1);

	/* Read the 4 high bits of the data */
	data = readl(lcd->virtbase + CHAR_RD) & 0xf0;

	/*
	 * The second read for the low bits does not trigger an IRQ
	 * so in this case we have to poll for the 4 lower bits
	 */
	i = 0;
	val = 0;
	while (!(val & CHAR_RAW_VALID) && i < 10) {
		udelay(100);
		val = readl(lcd->virtbase + CHAR_RAW);
		i++;
	}
	writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
	msleep(1);

	/* Read the 4 low bits of the data */
	data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f;

	return data;
}

static bool charlcd_4bit_read_bf(struct charlcd *lcd)
{
	if (lcd->irq >= 0) {
		/*
		 * If we'll use IRQs to wait for the busyflag, clear any
		 * pending flag and enable IRQ
		 */
		writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW);
		init_completion(&lcd->complete);
		writel(0x01, lcd->virtbase + CHAR_MASK);
	}
	readl(lcd->virtbase + CHAR_COM);
	return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false;
}

static void charlcd_4bit_wait_busy(struct charlcd *lcd)
{
	int retries = 50;

	udelay(100);
	while (charlcd_4bit_read_bf(lcd) && retries)
		retries--;
	if (!retries)
		dev_err(lcd->dev, "timeout waiting for busyflag\n");
}

static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd)
{
	u32 cmdlo = (cmd << 4) & 0xf0;
	u32 cmdhi = (cmd & 0xf0);

	writel(cmdhi, lcd->virtbase + CHAR_COM);
	udelay(10);
	writel(cmdlo, lcd->virtbase + CHAR_COM);
	charlcd_4bit_wait_busy(lcd);
}

static void charlcd_4bit_char(struct charlcd *lcd, u8 ch)
{
	u32 chlo = (ch << 4) & 0xf0;
	u32 chhi = (ch & 0xf0);

	writel(chhi, lcd->virtbase + CHAR_DAT);
	udelay(10);
	writel(chlo, lcd->virtbase + CHAR_DAT);
	charlcd_4bit_wait_busy(lcd);
}

static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str)
{
	u8 offset;
	int i;

	/*
	 * We support line 0, 1
	 * Line 1 runs from 0x00..0x27
	 * Line 2 runs from 0x28..0x4f
	 */
	if (line == 0)
		offset = 0;
	else if (line == 1)
		offset = 0x28;
	else
		return;

	/* Set offset */
	charlcd_4bit_command(lcd, HD_SET_DDRAM | offset);

	/* Send string */
	for (i = 0; i < strlen(str) && i < 0x28; i++)
		charlcd_4bit_char(lcd, str[i]);
}

static void charlcd_4bit_init(struct charlcd *lcd)
{
	/* These commands cannot be checked with the busy flag */
	writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
	msleep(5);
	writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
	udelay(100);
	writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM);
	udelay(100);
	/* Go to 4bit mode */
	writel(HD_FUNCSET, lcd->virtbase + CHAR_COM);
	udelay(100);
	/*
	 * 4bit mode, 2 lines, 5x8 font, after this the number of lines
	 * and the font cannot be changed until the next initialization sequence
	 */
	charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES);
	charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
	charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT);
	charlcd_4bit_command(lcd, HD_CLEAR);
	charlcd_4bit_command(lcd, HD_HOME);
	/* Put something useful in the display */
	charlcd_4bit_print(lcd, 0, "ARM Linux");
	charlcd_4bit_print(lcd, 1, UTS_RELEASE);
}

static void charlcd_init_work(struct work_struct *work)
{
	struct charlcd *lcd =
		container_of(work, struct charlcd, init_work.work);

	charlcd_4bit_init(lcd);
}

static int __init charlcd_probe(struct platform_device *pdev)
{
	int ret;
	struct charlcd *lcd;
	struct resource *res;

	lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL);
	if (!lcd)
		return -ENOMEM;

	lcd->dev = &pdev->dev;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		ret = -ENOENT;
		goto out_no_resource;
	}
	lcd->phybase = res->start;
	lcd->physize = resource_size(res);

	if (request_mem_region(lcd->phybase, lcd->physize,
			       DRIVERNAME) == NULL) {
		ret = -EBUSY;
		goto out_no_memregion;
	}

	lcd->virtbase = ioremap(lcd->phybase, lcd->physize);
	if (!lcd->virtbase) {
		ret = -ENOMEM;
		goto out_no_memregion;
	}

	lcd->irq = platform_get_irq(pdev, 0);
	/* If no IRQ is supplied, we'll survive without it */
	if (lcd->irq >= 0) {
		if (request_irq(lcd->irq, charlcd_interrupt, 0,
				DRIVERNAME, lcd)) {
			ret = -EIO;
			goto out_no_irq;
		}
	}

	platform_set_drvdata(pdev, lcd);

	/*
	 * Initialize the display in a delayed work, because
	 * it is VERY slow and would slow down the boot of the system.
	 */
	INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work);
	schedule_delayed_work(&lcd->init_work, 0);

	dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n",
		lcd->phybase);

	return 0;

out_no_irq:
	iounmap(lcd->virtbase);
out_no_memregion:
	release_mem_region(lcd->phybase, SZ_4K);
out_no_resource:
	kfree(lcd);
	return ret;
}

static int __exit charlcd_remove(struct platform_device *pdev)
{
	struct charlcd *lcd = platform_get_drvdata(pdev);

	if (lcd) {
		free_irq(lcd->irq, lcd);
		iounmap(lcd->virtbase);
		release_mem_region(lcd->phybase, lcd->physize);
		kfree(lcd);
	}

	return 0;
}

static int charlcd_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct charlcd *lcd = platform_get_drvdata(pdev);

	/* Power the display off */
	charlcd_4bit_command(lcd, HD_DISPCTRL);
	return 0;
}

static int charlcd_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct charlcd *lcd = platform_get_drvdata(pdev);

	/* Turn the display back on */
	charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON);
	return 0;
}

static const struct dev_pm_ops charlcd_pm_ops = {
	.suspend = charlcd_suspend,
	.resume = charlcd_resume,
};

static const struct of_device_id charlcd_match[] = {
	{ .compatible = "arm,versatile-lcd", },
	{}
};

static struct platform_driver charlcd_driver = {
	.driver = {
		.name = DRIVERNAME,
		.pm = &charlcd_pm_ops,
		.of_match_table = of_match_ptr(charlcd_match),
	},
	.remove = __exit_p(charlcd_remove),
};

module_platform_driver_probe(charlcd_driver, charlcd_probe);

MODULE_AUTHOR("Linus Walleij <triad@df.lth.se>");
MODULE_DESCRIPTION("ARM Character LCD Driver");
MODULE_LICENSE("GPL v2");
(((X) & ETH_RX_LEN) >> ETH_RX_LEN_BIT) /* Information that need to be kept for each board. */ struct korina_private { struct eth_regs *eth_regs; struct dma_reg *rx_dma_regs; struct dma_reg *tx_dma_regs; struct dma_desc *td_ring; /* transmit descriptor ring */ struct dma_desc *rd_ring; /* receive descriptor ring */ struct sk_buff *tx_skb[KORINA_NUM_TDS]; struct sk_buff *rx_skb[KORINA_NUM_RDS]; int rx_next_done; int rx_chain_head; int rx_chain_tail; enum chain_status rx_chain_status; int tx_next_done; int tx_chain_head; int tx_chain_tail; enum chain_status tx_chain_status; int tx_count; int tx_full; int rx_irq; int tx_irq; int ovr_irq; int und_irq; spinlock_t lock; /* NIC xmit lock */ int dma_halt_cnt; int dma_run_cnt; struct napi_struct napi; struct timer_list media_check_timer; struct mii_if_info mii_if; struct work_struct restart_task; struct net_device *dev; int phy_addr; }; extern unsigned int idt_cpu_freq; static inline void korina_start_dma(struct dma_reg *ch, u32 dma_addr) { writel(0, &ch->dmandptr); writel(dma_addr, &ch->dmadptr); } static inline void korina_abort_dma(struct net_device *dev, struct dma_reg *ch) { if (readl(&ch->dmac) & DMA_CHAN_RUN_BIT) { writel(0x10, &ch->dmac); while (!(readl(&ch->dmas) & DMA_STAT_HALT)) dev->trans_start = jiffies; writel(0, &ch->dmas); } writel(0, &ch->dmadptr); writel(0, &ch->dmandptr); } static inline void korina_chain_dma(struct dma_reg *ch, u32 dma_addr) { writel(dma_addr, &ch->dmandptr); } static void korina_abort_tx(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); korina_abort_dma(dev, lp->tx_dma_regs); } static void korina_abort_rx(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); korina_abort_dma(dev, lp->rx_dma_regs); } static void korina_start_rx(struct korina_private *lp, struct dma_desc *rd) { korina_start_dma(lp->rx_dma_regs, CPHYSADDR(rd)); } static void korina_chain_rx(struct korina_private *lp, struct dma_desc *rd) { korina_chain_dma(lp->rx_dma_regs, CPHYSADDR(rd)); } /* transmit packet */ static int korina_send_packet(struct sk_buff *skb, struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); unsigned long flags; u32 length; u32 chain_prev, chain_next; struct dma_desc *td; spin_lock_irqsave(&lp->lock, flags); td = &lp->td_ring[lp->tx_chain_tail]; /* stop queue when full, drop pkts if queue already full */ if (lp->tx_count >= (KORINA_NUM_TDS - 2)) { lp->tx_full = 1; if (lp->tx_count == (KORINA_NUM_TDS - 2)) netif_stop_queue(dev); else { dev->stats.tx_dropped++; dev_kfree_skb_any(skb); spin_unlock_irqrestore(&lp->lock, flags); return NETDEV_TX_BUSY; } } lp->tx_count++; lp->tx_skb[lp->tx_chain_tail] = skb; length = skb->len; dma_cache_wback((u32)skb->data, skb->len); /* Setup the transmit descriptor. */ dma_cache_inv((u32) td, sizeof(*td)); td->ca = CPHYSADDR(skb->data); chain_prev = (lp->tx_chain_tail - 1) & KORINA_TDS_MASK; chain_next = (lp->tx_chain_tail + 1) & KORINA_TDS_MASK; if (readl(&(lp->tx_dma_regs->dmandptr)) == 0) { if (lp->tx_chain_status == desc_empty) { /* Update tail */ td->control = DMA_COUNT(length) | DMA_DESC_COF | DMA_DESC_IOF; /* Move tail */ lp->tx_chain_tail = chain_next; /* Write to NDPTR */ writel(CPHYSADDR(&lp->td_ring[lp->tx_chain_head]), &lp->tx_dma_regs->dmandptr); /* Move head to tail */ lp->tx_chain_head = lp->tx_chain_tail; } else { /* Update tail */ td->control = DMA_COUNT(length) | DMA_DESC_COF | DMA_DESC_IOF; /* Link to prev */ lp->td_ring[chain_prev].control &= ~DMA_DESC_COF; /* Link to prev */ lp->td_ring[chain_prev].link = CPHYSADDR(td); /* Move tail */ lp->tx_chain_tail = chain_next; /* Write to NDPTR */ writel(CPHYSADDR(&lp->td_ring[lp->tx_chain_head]), &(lp->tx_dma_regs->dmandptr)); /* Move head to tail */ lp->tx_chain_head = lp->tx_chain_tail; lp->tx_chain_status = desc_empty; } } else { if (lp->tx_chain_status == desc_empty) { /* Update tail */ td->control = DMA_COUNT(length) | DMA_DESC_COF | DMA_DESC_IOF; /* Move tail */ lp->tx_chain_tail = chain_next; lp->tx_chain_status = desc_filled; } else { /* Update tail */ td->control = DMA_COUNT(length) | DMA_DESC_COF | DMA_DESC_IOF; lp->td_ring[chain_prev].control &= ~DMA_DESC_COF; lp->td_ring[chain_prev].link = CPHYSADDR(td); lp->tx_chain_tail = chain_next; } } dma_cache_wback((u32) td, sizeof(*td)); dev->trans_start = jiffies; spin_unlock_irqrestore(&lp->lock, flags); return NETDEV_TX_OK; } static int mdio_read(struct net_device *dev, int mii_id, int reg) { struct korina_private *lp = netdev_priv(dev); int ret; mii_id = ((lp->rx_irq == 0x2c ? 1 : 0) << 8); writel(0, &lp->eth_regs->miimcfg); writel(0, &lp->eth_regs->miimcmd); writel(mii_id | reg, &lp->eth_regs->miimaddr); writel(ETH_MII_CMD_SCN, &lp->eth_regs->miimcmd); ret = (int)(readl(&lp->eth_regs->miimrdd)); return ret; } static void mdio_write(struct net_device *dev, int mii_id, int reg, int val) { struct korina_private *lp = netdev_priv(dev); mii_id = ((lp->rx_irq == 0x2c ? 1 : 0) << 8); writel(0, &lp->eth_regs->miimcfg); writel(1, &lp->eth_regs->miimcmd); writel(mii_id | reg, &lp->eth_regs->miimaddr); writel(ETH_MII_CMD_SCN, &lp->eth_regs->miimcmd); writel(val, &lp->eth_regs->miimwtd); } /* Ethernet Rx DMA interrupt */ static irqreturn_t korina_rx_dma_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; struct korina_private *lp = netdev_priv(dev); u32 dmas, dmasm; irqreturn_t retval; dmas = readl(&lp->rx_dma_regs->dmas); if (dmas & (DMA_STAT_DONE | DMA_STAT_HALT | DMA_STAT_ERR)) { dmasm = readl(&lp->rx_dma_regs->dmasm); writel(dmasm | (DMA_STAT_DONE | DMA_STAT_HALT | DMA_STAT_ERR), &lp->rx_dma_regs->dmasm); napi_schedule(&lp->napi); if (dmas & DMA_STAT_ERR) printk(KERN_ERR "%s: DMA error\n", dev->name); retval = IRQ_HANDLED; } else retval = IRQ_NONE; return retval; } static int korina_rx(struct net_device *dev, int limit) { struct korina_private *lp = netdev_priv(dev); struct dma_desc *rd = &lp->rd_ring[lp->rx_next_done]; struct sk_buff *skb, *skb_new; u8 *pkt_buf; u32 devcs, pkt_len, dmas; int count; dma_cache_inv((u32)rd, sizeof(*rd)); for (count = 0; count < limit; count++) { skb = lp->rx_skb[lp->rx_next_done]; skb_new = NULL; devcs = rd->devcs; if ((KORINA_RBSIZE - (u32)DMA_COUNT(rd->control)) == 0) break; /* Update statistics counters */ if (devcs & ETH_RX_CRC) dev->stats.rx_crc_errors++; if (devcs & ETH_RX_LOR) dev->stats.rx_length_errors++; if (devcs & ETH_RX_LE) dev->stats.rx_length_errors++; if (devcs & ETH_RX_OVR) dev->stats.rx_fifo_errors++; if (devcs & ETH_RX_CV) dev->stats.rx_frame_errors++; if (devcs & ETH_RX_CES) dev->stats.rx_length_errors++; if (devcs & ETH_RX_MP) dev->stats.multicast++; if ((devcs & ETH_RX_LD) != ETH_RX_LD) { /* check that this is a whole packet * WARNING: DMA_FD bit incorrectly set * in Rc32434 (errata ref #077) */ dev->stats.rx_errors++; dev->stats.rx_dropped++; } else if ((devcs & ETH_RX_ROK)) { pkt_len = RCVPKT_LENGTH(devcs); /* must be the (first and) last * descriptor then */ pkt_buf = (u8 *)lp->rx_skb[lp->rx_next_done]->data; /* invalidate the cache */ dma_cache_inv((unsigned long)pkt_buf, pkt_len - 4); /* Malloc up new buffer. */ skb_new = netdev_alloc_skb_ip_align(dev, KORINA_RBSIZE); if (!skb_new) break; /* Do not count the CRC */ skb_put(skb, pkt_len - 4); skb->protocol = eth_type_trans(skb, dev); /* Pass the packet to upper layers */ netif_receive_skb(skb); dev->stats.rx_packets++; dev->stats.rx_bytes += pkt_len; /* Update the mcast stats */ if (devcs & ETH_RX_MP) dev->stats.multicast++; lp->rx_skb[lp->rx_next_done] = skb_new; } rd->devcs = 0; /* Restore descriptor's curr_addr */ if (skb_new) rd->ca = CPHYSADDR(skb_new->data); else rd->ca = CPHYSADDR(skb->data); rd->control = DMA_COUNT(KORINA_RBSIZE) | DMA_DESC_COD | DMA_DESC_IOD; lp->rd_ring[(lp->rx_next_done - 1) & KORINA_RDS_MASK].control &= ~DMA_DESC_COD; lp->rx_next_done = (lp->rx_next_done + 1) & KORINA_RDS_MASK; dma_cache_wback((u32)rd, sizeof(*rd)); rd = &lp->rd_ring[lp->rx_next_done]; writel(~DMA_STAT_DONE, &lp->rx_dma_regs->dmas); } dmas = readl(&lp->rx_dma_regs->dmas); if (dmas & DMA_STAT_HALT) { writel(~(DMA_STAT_HALT | DMA_STAT_ERR), &lp->rx_dma_regs->dmas); lp->dma_halt_cnt++; rd->devcs = 0; skb = lp->rx_skb[lp->rx_next_done]; rd->ca = CPHYSADDR(skb->data); dma_cache_wback((u32)rd, sizeof(*rd)); korina_chain_rx(lp, rd); } return count; } static int korina_poll(struct napi_struct *napi, int budget) { struct korina_private *lp = container_of(napi, struct korina_private, napi); struct net_device *dev = lp->dev; int work_done; work_done = korina_rx(dev, budget); if (work_done < budget) { napi_complete(napi); writel(readl(&lp->rx_dma_regs->dmasm) & ~(DMA_STAT_DONE | DMA_STAT_HALT | DMA_STAT_ERR), &lp->rx_dma_regs->dmasm); } return work_done; } /* * Set or clear the multicast filter for this adaptor. */ static void korina_multicast_list(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); unsigned long flags; struct netdev_hw_addr *ha; u32 recognise = ETH_ARC_AB; /* always accept broadcasts */ int i; /* Set promiscuous mode */ if (dev->flags & IFF_PROMISC) recognise |= ETH_ARC_PRO; else if ((dev->flags & IFF_ALLMULTI) || (netdev_mc_count(dev) > 4)) /* All multicast and broadcast */ recognise |= ETH_ARC_AM; /* Build the hash table */ if (netdev_mc_count(dev) > 4) { u16 hash_table[4]; u32 crc; for (i = 0; i < 4; i++) hash_table[i] = 0; netdev_for_each_mc_addr(ha, dev) { char *addrs = ha->addr; if (!(*addrs & 1)) continue; crc = ether_crc_le(6, addrs); crc >>= 26; hash_table[crc >> 4] |= 1 << (15 - (crc & 0xf)); } /* Accept filtered multicast */ recognise |= ETH_ARC_AFM; /* Fill the MAC hash tables with their values */ writel((u32)(hash_table[1] << 16 | hash_table[0]), &lp->eth_regs->ethhash0); writel((u32)(hash_table[3] << 16 | hash_table[2]), &lp->eth_regs->ethhash1); } spin_lock_irqsave(&lp->lock, flags); writel(recognise, &lp->eth_regs->etharc); spin_unlock_irqrestore(&lp->lock, flags); } static void korina_tx(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); struct dma_desc *td = &lp->td_ring[lp->tx_next_done]; u32 devcs; u32 dmas; spin_lock(&lp->lock); /* Process all desc that are done */ while (IS_DMA_FINISHED(td->control)) { if (lp->tx_full == 1) { netif_wake_queue(dev); lp->tx_full = 0; } devcs = lp->td_ring[lp->tx_next_done].devcs; if ((devcs & (ETH_TX_FD | ETH_TX_LD)) != (ETH_TX_FD | ETH_TX_LD)) { dev->stats.tx_errors++; dev->stats.tx_dropped++; /* Should never happen */ printk(KERN_ERR "%s: split tx ignored\n", dev->name); } else if (devcs & ETH_TX_TOK) { dev->stats.tx_packets++; dev->stats.tx_bytes += lp->tx_skb[lp->tx_next_done]->len; } else { dev->stats.tx_errors++; dev->stats.tx_dropped++; /* Underflow */ if (devcs & ETH_TX_UND) dev->stats.tx_fifo_errors++; /* Oversized frame */ if (devcs & ETH_TX_OF) dev->stats.tx_aborted_errors++; /* Excessive deferrals */ if (devcs & ETH_TX_ED) dev->stats.tx_carrier_errors++; /* Collisions: medium busy */ if (devcs & ETH_TX_EC) dev->stats.collisions++; /* Late collision */ if (devcs & ETH_TX_LC) dev->stats.tx_window_errors++; } /* We must always free the original skb */ if (lp->tx_skb[lp->tx_next_done]) { dev_kfree_skb_any(lp->tx_skb[lp->tx_next_done]); lp->tx_skb[lp->tx_next_done] = NULL; } lp->td_ring[lp->tx_next_done].control = DMA_DESC_IOF; lp->td_ring[lp->tx_next_done].devcs = ETH_TX_FD | ETH_TX_LD; lp->td_ring[lp->tx_next_done].link = 0; lp->td_ring[lp->tx_next_done].ca = 0; lp->tx_count--; /* Go on to next transmission */ lp->tx_next_done = (lp->tx_next_done + 1) & KORINA_TDS_MASK; td = &lp->td_ring[lp->tx_next_done]; } /* Clear the DMA status register */ dmas = readl(&lp->tx_dma_regs->dmas); writel(~dmas, &lp->tx_dma_regs->dmas); writel(readl(&lp->tx_dma_regs->dmasm) & ~(DMA_STAT_FINI | DMA_STAT_ERR), &lp->tx_dma_regs->dmasm); spin_unlock(&lp->lock); } static irqreturn_t korina_tx_dma_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; struct korina_private *lp = netdev_priv(dev); u32 dmas, dmasm; irqreturn_t retval; dmas = readl(&lp->tx_dma_regs->dmas); if (dmas & (DMA_STAT_FINI | DMA_STAT_ERR)) { dmasm = readl(&lp->tx_dma_regs->dmasm); writel(dmasm | (DMA_STAT_FINI | DMA_STAT_ERR), &lp->tx_dma_regs->dmasm); korina_tx(dev); if (lp->tx_chain_status == desc_filled && (readl(&(lp->tx_dma_regs->dmandptr)) == 0)) { writel(CPHYSADDR(&lp->td_ring[lp->tx_chain_head]), &(lp->tx_dma_regs->dmandptr)); lp->tx_chain_status = desc_empty; lp->tx_chain_head = lp->tx_chain_tail; dev->trans_start = jiffies; } if (dmas & DMA_STAT_ERR) printk(KERN_ERR "%s: DMA error\n", dev->name); retval = IRQ_HANDLED; } else retval = IRQ_NONE; return retval; } static void korina_check_media(struct net_device *dev, unsigned int init_media) { struct korina_private *lp = netdev_priv(dev); mii_check_media(&lp->mii_if, 0, init_media); if (lp->mii_if.full_duplex) writel(readl(&lp->eth_regs->ethmac2) | ETH_MAC2_FD, &lp->eth_regs->ethmac2); else writel(readl(&lp->eth_regs->ethmac2) & ~ETH_MAC2_FD, &lp->eth_regs->ethmac2); } static void korina_poll_media(unsigned long data) { struct net_device *dev = (struct net_device *) data; struct korina_private *lp = netdev_priv(dev); korina_check_media(dev, 0); mod_timer(&lp->media_check_timer, jiffies + HZ); } static void korina_set_carrier(struct mii_if_info *mii) { if (mii->force_media) { /* autoneg is off: Link is always assumed to be up */ if (!netif_carrier_ok(mii->dev)) netif_carrier_on(mii->dev); } else /* Let MMI library update carrier status */ korina_check_media(mii->dev, 0); } static int korina_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct korina_private *lp = netdev_priv(dev); struct mii_ioctl_data *data = if_mii(rq); int rc; if (!netif_running(dev)) return -EINVAL; spin_lock_irq(&lp->lock); rc = generic_mii_ioctl(&lp->mii_if, data, cmd, NULL); spin_unlock_irq(&lp->lock); korina_set_carrier(&lp->mii_if); return rc; } /* ethtool helpers */ static void netdev_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { struct korina_private *lp = netdev_priv(dev); strcpy(info->driver, DRV_NAME); strcpy(info->version, DRV_VERSION); strcpy(info->bus_info, lp->dev->name); } static int netdev_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct korina_private *lp = netdev_priv(dev); int rc; spin_lock_irq(&lp->lock); rc = mii_ethtool_gset(&lp->mii_if, cmd); spin_unlock_irq(&lp->lock); return rc; } static int netdev_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct korina_private *lp = netdev_priv(dev); int rc; spin_lock_irq(&lp->lock); rc = mii_ethtool_sset(&lp->mii_if, cmd); spin_unlock_irq(&lp->lock); korina_set_carrier(&lp->mii_if); return rc; } static u32 netdev_get_link(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); return mii_link_ok(&lp->mii_if); } static const struct ethtool_ops netdev_ethtool_ops = { .get_drvinfo = netdev_get_drvinfo, .get_settings = netdev_get_settings, .set_settings = netdev_set_settings, .get_link = netdev_get_link, }; static int korina_alloc_ring(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); struct sk_buff *skb; int i; /* Initialize the transmit descriptors */ for (i = 0; i < KORINA_NUM_TDS; i++) { lp->td_ring[i].control = DMA_DESC_IOF; lp->td_ring[i].devcs = ETH_TX_FD | ETH_TX_LD; lp->td_ring[i].ca = 0; lp->td_ring[i].link = 0; } lp->tx_next_done = lp->tx_chain_head = lp->tx_chain_tail = lp->tx_full = lp->tx_count = 0; lp->tx_chain_status = desc_empty; /* Initialize the receive descriptors */ for (i = 0; i < KORINA_NUM_RDS; i++) { skb = netdev_alloc_skb_ip_align(dev, KORINA_RBSIZE); if (!skb) return -ENOMEM; lp->rx_skb[i] = skb; lp->rd_ring[i].control = DMA_DESC_IOD | DMA_COUNT(KORINA_RBSIZE); lp->rd_ring[i].devcs = 0; lp->rd_ring[i].ca = CPHYSADDR(skb->data); lp->rd_ring[i].link = CPHYSADDR(&lp->rd_ring[i+1]); } /* loop back receive descriptors, so the last * descriptor points to the first one */ lp->rd_ring[i - 1].link = CPHYSADDR(&lp->rd_ring[0]); lp->rd_ring[i - 1].control |= DMA_DESC_COD; lp->rx_next_done = 0; lp->rx_chain_head = 0; lp->rx_chain_tail = 0; lp->rx_chain_status = desc_empty; return 0; } static void korina_free_ring(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); int i; for (i = 0; i < KORINA_NUM_RDS; i++) { lp->rd_ring[i].control = 0; if (lp->rx_skb[i]) dev_kfree_skb_any(lp->rx_skb[i]); lp->rx_skb[i] = NULL; } for (i = 0; i < KORINA_NUM_TDS; i++) { lp->td_ring[i].control = 0; if (lp->tx_skb[i]) dev_kfree_skb_any(lp->tx_skb[i]); lp->tx_skb[i] = NULL; } } /* * Initialize the RC32434 ethernet controller. */ static int korina_init(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); /* Disable DMA */ korina_abort_tx(dev); korina_abort_rx(dev); /* reset ethernet logic */ writel(0, &lp->eth_regs->ethintfc); while ((readl(&lp->eth_regs->ethintfc) & ETH_INT_FC_RIP)) dev->trans_start = jiffies; /* Enable Ethernet Interface */ writel(ETH_INT_FC_EN, &lp->eth_regs->ethintfc); /* Allocate rings */ if (korina_alloc_ring(dev)) { printk(KERN_ERR "%s: descriptor allocation failed\n", dev->name); korina_free_ring(dev); return -ENOMEM; } writel(0, &lp->rx_dma_regs->dmas); /* Start Rx DMA */ korina_start_rx(lp, &lp->rd_ring[0]); writel(readl(&lp->tx_dma_regs->dmasm) & ~(DMA_STAT_FINI | DMA_STAT_ERR), &lp->tx_dma_regs->dmasm); writel(readl(&lp->rx_dma_regs->dmasm) & ~(DMA_STAT_DONE | DMA_STAT_HALT | DMA_STAT_ERR), &lp->rx_dma_regs->dmasm); /* Accept only packets destined for this Ethernet device address */ writel(ETH_ARC_AB, &lp->eth_regs->etharc); /* Set all Ether station address registers to their initial values */ writel(STATION_ADDRESS_LOW(dev), &lp->eth_regs->ethsal0); writel(STATION_ADDRESS_HIGH(dev), &lp->eth_regs->ethsah0); writel(STATION_ADDRESS_LOW(dev), &lp->eth_regs->ethsal1); writel(STATION_ADDRESS_HIGH(dev), &lp->eth_regs->ethsah1); writel(STATION_ADDRESS_LOW(dev), &lp->eth_regs->ethsal2); writel(STATION_ADDRESS_HIGH(dev), &lp->eth_regs->ethsah2); writel(STATION_ADDRESS_LOW(dev), &lp->eth_regs->ethsal3); writel(STATION_ADDRESS_HIGH(dev), &lp->eth_regs->ethsah3); /* Frame Length Checking, Pad Enable, CRC Enable, Full Duplex set */ writel(ETH_MAC2_PE | ETH_MAC2_CEN | ETH_MAC2_FD, &lp->eth_regs->ethmac2); /* Back to back inter-packet-gap */ writel(0x15, &lp->eth_regs->ethipgt); /* Non - Back to back inter-packet-gap */ writel(0x12, &lp->eth_regs->ethipgr); /* Management Clock Prescaler Divisor * Clock independent setting */ writel(((idt_cpu_freq) / MII_CLOCK + 1) & ~1, &lp->eth_regs->ethmcp); /* don't transmit until fifo contains 48b */ writel(48, &lp->eth_regs->ethfifott); writel(ETH_MAC1_RE, &lp->eth_regs->ethmac1); napi_enable(&lp->napi); netif_start_queue(dev); return 0; } /* * Restart the RC32434 ethernet controller. */ static void korina_restart_task(struct work_struct *work) { struct korina_private *lp = container_of(work, struct korina_private, restart_task); struct net_device *dev = lp->dev; /* * Disable interrupts */ disable_irq(lp->rx_irq); disable_irq(lp->tx_irq); disable_irq(lp->ovr_irq); disable_irq(lp->und_irq); writel(readl(&lp->tx_dma_regs->dmasm) | DMA_STAT_FINI | DMA_STAT_ERR, &lp->tx_dma_regs->dmasm); writel(readl(&lp->rx_dma_regs->dmasm) | DMA_STAT_DONE | DMA_STAT_HALT | DMA_STAT_ERR, &lp->rx_dma_regs->dmasm); korina_free_ring(dev); napi_disable(&lp->napi); if (korina_init(dev) < 0) { printk(KERN_ERR "%s: cannot restart device\n", dev->name); return; } korina_multicast_list(dev); enable_irq(lp->und_irq); enable_irq(lp->ovr_irq); enable_irq(lp->tx_irq); enable_irq(lp->rx_irq); } static void korina_clear_and_restart(struct net_device *dev, u32 value) { struct korina_private *lp = netdev_priv(dev); netif_stop_queue(dev); writel(value, &lp->eth_regs->ethintfc); schedule_work(&lp->restart_task); } /* Ethernet Tx Underflow interrupt */ static irqreturn_t korina_und_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; struct korina_private *lp = netdev_priv(dev); unsigned int und; spin_lock(&lp->lock); und = readl(&lp->eth_regs->ethintfc); if (und & ETH_INT_FC_UND) korina_clear_and_restart(dev, und & ~ETH_INT_FC_UND); spin_unlock(&lp->lock); return IRQ_HANDLED; } static void korina_tx_timeout(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); schedule_work(&lp->restart_task); } /* Ethernet Rx Overflow interrupt */ static irqreturn_t korina_ovr_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; struct korina_private *lp = netdev_priv(dev); unsigned int ovr; spin_lock(&lp->lock); ovr = readl(&lp->eth_regs->ethintfc); if (ovr & ETH_INT_FC_OVR) korina_clear_and_restart(dev, ovr & ~ETH_INT_FC_OVR); spin_unlock(&lp->lock); return IRQ_HANDLED; } #ifdef CONFIG_NET_POLL_CONTROLLER static void korina_poll_controller(struct net_device *dev) { disable_irq(dev->irq); korina_tx_dma_interrupt(dev->irq, dev); enable_irq(dev->irq); } #endif static int korina_open(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); int ret; /* Initialize */ ret = korina_init(dev); if (ret < 0) { printk(KERN_ERR "%s: cannot open device\n", dev->name); goto out; } /* Install the interrupt handler * that handles the Done Finished * Ovr and Und Events */ ret = request_irq(lp->rx_irq, korina_rx_dma_interrupt, IRQF_DISABLED, "Korina ethernet Rx", dev); if (ret < 0) { printk(KERN_ERR "%s: unable to get Rx DMA IRQ %d\n", dev->name, lp->rx_irq); goto err_release; } ret = request_irq(lp->tx_irq, korina_tx_dma_interrupt, IRQF_DISABLED, "Korina ethernet Tx", dev); if (ret < 0) { printk(KERN_ERR "%s: unable to get Tx DMA IRQ %d\n", dev->name, lp->tx_irq); goto err_free_rx_irq; } /* Install handler for overrun error. */ ret = request_irq(lp->ovr_irq, korina_ovr_interrupt, IRQF_DISABLED, "Ethernet Overflow", dev); if (ret < 0) { printk(KERN_ERR "%s: unable to get OVR IRQ %d\n", dev->name, lp->ovr_irq); goto err_free_tx_irq; } /* Install handler for underflow error. */ ret = request_irq(lp->und_irq, korina_und_interrupt, IRQF_DISABLED, "Ethernet Underflow", dev); if (ret < 0) { printk(KERN_ERR "%s: unable to get UND IRQ %d\n", dev->name, lp->und_irq); goto err_free_ovr_irq; } mod_timer(&lp->media_check_timer, jiffies + 1); out: return ret; err_free_ovr_irq: free_irq(lp->ovr_irq, dev); err_free_tx_irq: free_irq(lp->tx_irq, dev); err_free_rx_irq: free_irq(lp->rx_irq, dev); err_release: korina_free_ring(dev); goto out; } static int korina_close(struct net_device *dev) { struct korina_private *lp = netdev_priv(dev); u32 tmp; del_timer(&lp->media_check_timer); /* Disable interrupts */ disable_irq(lp->rx_irq); disable_irq(lp->tx_irq); disable_irq(lp->ovr_irq); disable_irq(lp->und_irq); korina_abort_tx(dev); tmp = readl(&lp->tx_dma_regs->dmasm); tmp = tmp | DMA_STAT_FINI | DMA_STAT_ERR; writel(tmp, &lp->tx_dma_regs->dmasm); korina_abort_rx(dev); tmp = readl(&lp->rx_dma_regs->dmasm); tmp = tmp | DMA_STAT_DONE | DMA_STAT_HALT | DMA_STAT_ERR; writel(tmp, &lp->rx_dma_regs->dmasm); korina_free_ring(dev); napi_disable(&lp->napi); cancel_work_sync(&lp->restart_task); free_irq(lp->rx_irq, dev); free_irq(lp->tx_irq, dev); free_irq(lp->ovr_irq, dev); free_irq(lp->und_irq, dev); return 0; } static const struct net_device_ops korina_netdev_ops = { .ndo_open = korina_open, .ndo_stop = korina_close, .ndo_start_xmit = korina_send_packet, .ndo_set_multicast_list = korina_multicast_list, .ndo_tx_timeout = korina_tx_timeout, .ndo_do_ioctl = korina_ioctl, .ndo_change_mtu = eth_change_mtu, .ndo_validate_addr = eth_validate_addr, .ndo_set_mac_address = eth_mac_addr, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = korina_poll_controller, #endif }; static int korina_probe(struct platform_device *pdev) { struct korina_device *bif = platform_get_drvdata(pdev); struct korina_private *lp; struct net_device *dev; struct resource *r; int rc; dev = alloc_etherdev(sizeof(struct korina_private)); if (!dev) { printk(KERN_ERR DRV_NAME ": alloc_etherdev failed\n"); return -ENOMEM; } SET_NETDEV_DEV(dev, &pdev->dev); lp = netdev_priv(dev); bif->dev = dev; memcpy(dev->dev_addr, bif->mac, 6); lp->rx_irq = platform_get_irq_byname(pdev, "korina_rx"); lp->tx_irq = platform_get_irq_byname(pdev, "korina_tx"); lp->ovr_irq = platform_get_irq_byname(pdev, "korina_ovr"); lp->und_irq = platform_get_irq_byname(pdev, "korina_und"); r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "korina_regs"); dev->base_addr = r->start; lp->eth_regs = ioremap_nocache(r->start, resource_size(r)); if (!lp->eth_regs) { printk(KERN_ERR DRV_NAME ": cannot remap registers\n"); rc = -ENXIO; goto probe_err_out; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "korina_dma_rx"); lp->rx_dma_regs = ioremap_nocache(r->start, resource_size(r)); if (!lp->rx_dma_regs) { printk(KERN_ERR DRV_NAME ": cannot remap Rx DMA registers\n"); rc = -ENXIO; goto probe_err_dma_rx; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "korina_dma_tx"); lp->tx_dma_regs = ioremap_nocache(r->start, resource_size(r)); if (!lp->tx_dma_regs) { printk(KERN_ERR DRV_NAME ": cannot remap Tx DMA registers\n"); rc = -ENXIO; goto probe_err_dma_tx; } lp->td_ring = kmalloc(TD_RING_SIZE + RD_RING_SIZE, GFP_KERNEL); if (!lp->td_ring) { printk(KERN_ERR DRV_NAME ": cannot allocate descriptors\n"); rc = -ENXIO; goto probe_err_td_ring; } dma_cache_inv((unsigned long)(lp->td_ring), TD_RING_SIZE + RD_RING_SIZE); /* now convert TD_RING pointer to KSEG1 */ lp->td_ring = (struct dma_desc *)KSEG1ADDR(lp->td_ring); lp->rd_ring = &lp->td_ring[KORINA_NUM_TDS]; spin_lock_init(&lp->lock); /* just use the rx dma irq */ dev->irq = lp->rx_irq; lp->dev = dev; dev->netdev_ops = &korina_netdev_ops; dev->ethtool_ops = &netdev_ethtool_ops; dev->watchdog_timeo = TX_TIMEOUT; netif_napi_add(dev, &lp->napi, korina_poll, 64); lp->phy_addr = (((lp->rx_irq == 0x2c? 1:0) << 8) | 0x05); lp->mii_if.dev = dev; lp->mii_if.mdio_read = mdio_read; lp->mii_if.mdio_write = mdio_write; lp->mii_if.phy_id = lp->phy_addr; lp->mii_if.phy_id_mask = 0x1f; lp->mii_if.reg_num_mask = 0x1f; rc = register_netdev(dev); if (rc < 0) { printk(KERN_ERR DRV_NAME ": cannot register net device: %d\n", rc); goto probe_err_register; } setup_timer(&lp->media_check_timer, korina_poll_media, (unsigned long) dev); INIT_WORK(&lp->restart_task, korina_restart_task); printk(KERN_INFO "%s: " DRV_NAME "-" DRV_VERSION " " DRV_RELDATE "\n", dev->name); out: return rc; probe_err_register: kfree(lp->td_ring); probe_err_td_ring: iounmap(lp->tx_dma_regs); probe_err_dma_tx: iounmap(lp->rx_dma_regs); probe_err_dma_rx: iounmap(lp->eth_regs); probe_err_out: free_netdev(dev); goto out; } static int korina_remove(struct platform_device *pdev) { struct korina_device *bif = platform_get_drvdata(pdev); struct korina_private *lp = netdev_priv(bif->dev); iounmap(lp->eth_regs); iounmap(lp->rx_dma_regs); iounmap(lp->tx_dma_regs); platform_set_drvdata(pdev, NULL); unregister_netdev(bif->dev); free_netdev(bif->dev); return 0; } static struct platform_driver korina_driver = { .driver.name = "korina", .probe = korina_probe, .remove = korina_remove, }; static int __init korina_init_module(void) { return platform_driver_register(&korina_driver); } static void korina_cleanup_module(void) { return platform_driver_unregister(&korina_driver); } module_init(korina_init_module); module_exit(korina_cleanup_module); MODULE_AUTHOR("Philip Rischel <rischelp@idt.com>"); MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>"); MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); MODULE_DESCRIPTION("IDT RC32434 (Korina) Ethernet driver"); MODULE_LICENSE("GPL");