162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * atari_nfeth.c - ARAnyM ethernet card driver for GNU/Linux
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (c) 2005 Milan Jurik, Petr Stehlik of ARAnyM dev team
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Based on ARAnyM driver for FreeMiNT written by Standa Opichal
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This software may be used and distributed according to the terms of
962306a36Sopenharmony_ci * the GNU General Public License (GPL), incorporated herein by reference.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define DRV_VERSION	"0.3"
1362306a36Sopenharmony_ci#define DRV_RELDATE	"10/12/2005"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/netdevice.h>
1862306a36Sopenharmony_ci#include <linux/etherdevice.h>
1962306a36Sopenharmony_ci#include <linux/interrupt.h>
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <asm/natfeat.h>
2262306a36Sopenharmony_ci#include <asm/virtconvert.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cienum {
2562306a36Sopenharmony_ci	GET_VERSION = 0,/* no parameters, return NFAPI_VERSION in d0 */
2662306a36Sopenharmony_ci	XIF_INTLEVEL,	/* no parameters, return Interrupt Level in d0 */
2762306a36Sopenharmony_ci	XIF_IRQ,	/* acknowledge interrupt from host */
2862306a36Sopenharmony_ci	XIF_START,	/* (ethX), called on 'ifup', start receiver thread */
2962306a36Sopenharmony_ci	XIF_STOP,	/* (ethX), called on 'ifdown', stop the thread */
3062306a36Sopenharmony_ci	XIF_READLENGTH,	/* (ethX), return size of network data block to read */
3162306a36Sopenharmony_ci	XIF_READBLOCK,	/* (ethX, buffer, size), read block of network data */
3262306a36Sopenharmony_ci	XIF_WRITEBLOCK,	/* (ethX, buffer, size), write block of network data */
3362306a36Sopenharmony_ci	XIF_GET_MAC,	/* (ethX, buffer, size), return MAC HW addr in buffer */
3462306a36Sopenharmony_ci	XIF_GET_IPHOST,	/* (ethX, buffer, size), return IP address of host */
3562306a36Sopenharmony_ci	XIF_GET_IPATARI,/* (ethX, buffer, size), return IP address of atari */
3662306a36Sopenharmony_ci	XIF_GET_NETMASK	/* (ethX, buffer, size), return IP netmask */
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define MAX_UNIT	8
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/* These identify the driver base version and may not be removed. */
4262306a36Sopenharmony_cistatic const char version[] =
4362306a36Sopenharmony_ci	KERN_INFO KBUILD_MODNAME ".c:v" DRV_VERSION " " DRV_RELDATE
4462306a36Sopenharmony_ci	" S.Opichal, M.Jurik, P.Stehlik\n"
4562306a36Sopenharmony_ci	KERN_INFO " http://aranym.org/\n";
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ciMODULE_AUTHOR("Milan Jurik");
4862306a36Sopenharmony_ciMODULE_DESCRIPTION("Atari NFeth driver");
4962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic long nfEtherID;
5362306a36Sopenharmony_cistatic int nfEtherIRQ;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistruct nfeth_private {
5662306a36Sopenharmony_ci	int ethX;
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic struct net_device *nfeth_dev[MAX_UNIT];
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic int nfeth_open(struct net_device *dev)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct nfeth_private *priv = netdev_priv(dev);
6462306a36Sopenharmony_ci	int res;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	res = nf_call(nfEtherID + XIF_START, priv->ethX);
6762306a36Sopenharmony_ci	netdev_dbg(dev, "%s: %d\n", __func__, res);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* Ready for data */
7062306a36Sopenharmony_ci	netif_start_queue(dev);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	return 0;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic int nfeth_stop(struct net_device *dev)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct nfeth_private *priv = netdev_priv(dev);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/* No more data */
8062306a36Sopenharmony_ci	netif_stop_queue(dev);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	nf_call(nfEtherID + XIF_STOP, priv->ethX);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return 0;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci/*
8862306a36Sopenharmony_ci * Read a packet out of the adapter and pass it to the upper layers
8962306a36Sopenharmony_ci */
9062306a36Sopenharmony_cistatic inline void recv_packet(struct net_device *dev)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct nfeth_private *priv = netdev_priv(dev);
9362306a36Sopenharmony_ci	unsigned short pktlen;
9462306a36Sopenharmony_ci	struct sk_buff *skb;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/* read packet length (excluding 32 bit crc) */
9762306a36Sopenharmony_ci	pktlen = nf_call(nfEtherID + XIF_READLENGTH, priv->ethX);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	netdev_dbg(dev, "%s: %u\n", __func__, pktlen);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (!pktlen) {
10262306a36Sopenharmony_ci		netdev_dbg(dev, "%s: pktlen == 0\n", __func__);
10362306a36Sopenharmony_ci		dev->stats.rx_errors++;
10462306a36Sopenharmony_ci		return;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	skb = dev_alloc_skb(pktlen + 2);
10862306a36Sopenharmony_ci	if (!skb) {
10962306a36Sopenharmony_ci		netdev_dbg(dev, "%s: out of mem (buf_alloc failed)\n",
11062306a36Sopenharmony_ci			   __func__);
11162306a36Sopenharmony_ci		dev->stats.rx_dropped++;
11262306a36Sopenharmony_ci		return;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	skb->dev = dev;
11662306a36Sopenharmony_ci	skb_reserve(skb, 2);		/* 16 Byte align  */
11762306a36Sopenharmony_ci	skb_put(skb, pktlen);		/* make room */
11862306a36Sopenharmony_ci	nf_call(nfEtherID + XIF_READBLOCK, priv->ethX, virt_to_phys(skb->data),
11962306a36Sopenharmony_ci		pktlen);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	skb->protocol = eth_type_trans(skb, dev);
12262306a36Sopenharmony_ci	netif_rx(skb);
12362306a36Sopenharmony_ci	dev->stats.rx_packets++;
12462306a36Sopenharmony_ci	dev->stats.rx_bytes += pktlen;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* and enqueue packet */
12762306a36Sopenharmony_ci	return;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic irqreturn_t nfeth_interrupt(int irq, void *dev_id)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	int i, m, mask;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	mask = nf_call(nfEtherID + XIF_IRQ, 0);
13562306a36Sopenharmony_ci	for (i = 0, m = 1; i < MAX_UNIT; m <<= 1, i++) {
13662306a36Sopenharmony_ci		if (mask & m && nfeth_dev[i]) {
13762306a36Sopenharmony_ci			recv_packet(nfeth_dev[i]);
13862306a36Sopenharmony_ci			nf_call(nfEtherID + XIF_IRQ, m);
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci	return IRQ_HANDLED;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int nfeth_xmit(struct sk_buff *skb, struct net_device *dev)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	unsigned int len;
14762306a36Sopenharmony_ci	char *data, shortpkt[ETH_ZLEN];
14862306a36Sopenharmony_ci	struct nfeth_private *priv = netdev_priv(dev);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	data = skb->data;
15162306a36Sopenharmony_ci	len = skb->len;
15262306a36Sopenharmony_ci	if (len < ETH_ZLEN) {
15362306a36Sopenharmony_ci		memset(shortpkt, 0, ETH_ZLEN);
15462306a36Sopenharmony_ci		memcpy(shortpkt, data, len);
15562306a36Sopenharmony_ci		data = shortpkt;
15662306a36Sopenharmony_ci		len = ETH_ZLEN;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	netdev_dbg(dev, "%s: send %u bytes\n", __func__, len);
16062306a36Sopenharmony_ci	nf_call(nfEtherID + XIF_WRITEBLOCK, priv->ethX, virt_to_phys(data),
16162306a36Sopenharmony_ci		len);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	dev->stats.tx_packets++;
16462306a36Sopenharmony_ci	dev->stats.tx_bytes += len;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	dev_kfree_skb(skb);
16762306a36Sopenharmony_ci	return 0;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic void nfeth_tx_timeout(struct net_device *dev, unsigned int txqueue)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	dev->stats.tx_errors++;
17362306a36Sopenharmony_ci	netif_wake_queue(dev);
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic const struct net_device_ops nfeth_netdev_ops = {
17762306a36Sopenharmony_ci	.ndo_open		= nfeth_open,
17862306a36Sopenharmony_ci	.ndo_stop		= nfeth_stop,
17962306a36Sopenharmony_ci	.ndo_start_xmit		= nfeth_xmit,
18062306a36Sopenharmony_ci	.ndo_tx_timeout		= nfeth_tx_timeout,
18162306a36Sopenharmony_ci	.ndo_validate_addr	= eth_validate_addr,
18262306a36Sopenharmony_ci	.ndo_set_mac_address	= eth_mac_addr,
18362306a36Sopenharmony_ci};
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic struct net_device * __init nfeth_probe(int unit)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct net_device *dev;
18862306a36Sopenharmony_ci	struct nfeth_private *priv;
18962306a36Sopenharmony_ci	char mac[ETH_ALEN], host_ip[32], local_ip[32];
19062306a36Sopenharmony_ci	int err;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	if (!nf_call(nfEtherID + XIF_GET_MAC, unit, virt_to_phys(mac),
19362306a36Sopenharmony_ci		     ETH_ALEN))
19462306a36Sopenharmony_ci		return NULL;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	dev = alloc_etherdev(sizeof(struct nfeth_private));
19762306a36Sopenharmony_ci	if (!dev)
19862306a36Sopenharmony_ci		return NULL;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	dev->irq = nfEtherIRQ;
20162306a36Sopenharmony_ci	dev->netdev_ops = &nfeth_netdev_ops;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	eth_hw_addr_set(dev, mac);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	priv = netdev_priv(dev);
20662306a36Sopenharmony_ci	priv->ethX = unit;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	err = register_netdev(dev);
20962306a36Sopenharmony_ci	if (err) {
21062306a36Sopenharmony_ci		free_netdev(dev);
21162306a36Sopenharmony_ci		return NULL;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	nf_call(nfEtherID + XIF_GET_IPHOST, unit,
21562306a36Sopenharmony_ci		virt_to_phys(host_ip), sizeof(host_ip));
21662306a36Sopenharmony_ci	nf_call(nfEtherID + XIF_GET_IPATARI, unit,
21762306a36Sopenharmony_ci		virt_to_phys(local_ip), sizeof(local_ip));
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	netdev_info(dev, KBUILD_MODNAME " addr:%s (%s) HWaddr:%pM\n", host_ip,
22062306a36Sopenharmony_ci		    local_ip, mac);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return dev;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int __init nfeth_init(void)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	long ver;
22862306a36Sopenharmony_ci	int error, i;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	nfEtherID = nf_get_id("ETHERNET");
23162306a36Sopenharmony_ci	if (!nfEtherID)
23262306a36Sopenharmony_ci		return -ENODEV;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	ver = nf_call(nfEtherID + GET_VERSION);
23562306a36Sopenharmony_ci	pr_info("API %lu\n", ver);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	nfEtherIRQ = nf_call(nfEtherID + XIF_INTLEVEL);
23862306a36Sopenharmony_ci	error = request_irq(nfEtherIRQ, nfeth_interrupt, IRQF_SHARED,
23962306a36Sopenharmony_ci			    "eth emu", nfeth_interrupt);
24062306a36Sopenharmony_ci	if (error) {
24162306a36Sopenharmony_ci		pr_err("request for irq %d failed %d", nfEtherIRQ, error);
24262306a36Sopenharmony_ci		return error;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	for (i = 0; i < MAX_UNIT; i++)
24662306a36Sopenharmony_ci		nfeth_dev[i] = nfeth_probe(i);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	return 0;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic void __exit nfeth_cleanup(void)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	int i;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	for (i = 0; i < MAX_UNIT; i++) {
25662306a36Sopenharmony_ci		if (nfeth_dev[i]) {
25762306a36Sopenharmony_ci			unregister_netdev(nfeth_dev[i]);
25862306a36Sopenharmony_ci			free_netdev(nfeth_dev[i]);
25962306a36Sopenharmony_ci		}
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci	free_irq(nfEtherIRQ, nfeth_interrupt);
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cimodule_init(nfeth_init);
26562306a36Sopenharmony_cimodule_exit(nfeth_cleanup);
266