/*
 *	d_link.c
 *
 *	Portions (C) Copyright 1993 by Bjorn Ekwall
 *	The Author may be reached as bj0rn@blox.se
 *
 *	Linux driver for the D-Link Ethernet pocket adapter.
 *	Based on sources from linux 0.99pl5
 *	and on a sample network driver core for linux.
 *
 *	Sample driver written 1993 by Donald Becker <becker@super.org>
 *	C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715
 *
 *	compile-command:
 *	"gcc -DKERNEL -Wall -O6 -fomit-frame-pointer -c d_link.c"
 */
/*************************************************************
 * If you have trouble reading/writing to the adapter,
 * uncomment the following "#define":
#define REALLY_SLOW_IO
 */

/* $Id: d_link.c,v 0.21 1993/06/02 19:41:00 waltje Exp $ */
/* $Log: d_link.c,v $
 * Revision 0.21  1993/06/02  19:41:00 waltje
 * Applied multi-IRQ fix from Bjorn Ekwall and increaded version
 * number to 0.21.
 *
 * Revision 0.20  1993/03/26  11:43:53  root
 * Changed version number to indicate "alpha+" (almost beta :-)
 *
 * Revision 0.16  1993/03/26  11:26:46  root
 * Last ALPHA-minus version.
 * REALLY_SLOW_IO choice included (at line 20)
 * SLOW_DOWN_IO added anyway in convenience macros/functions
 * Test of D_LINK_FIFO included (not completely debugged)
 *
 * Revision 0.15  1993/03/24  14:00:49  root
 * Modified the interrupt handling considerably.
 * (The .asm source had me fooled in how it _really_ works :-)
 *
 * Revision 0.14  1993/03/21  01:57:25  root
 * Modified the interrupthandler for more robustness (hopefully :-)
 *
 * Revision 0.13  1993/03/19  11:45:09  root
 * Re-write of ALPHA release using Don Beckers skeleton (still works, kind of ...:-)
 *
 * Revision 0.12  1993/03/16  13:22:21  root
 * working ALPHA-release
 *
 */

static char *version =
	"d_link.c: $Revision: 0.21 $,  Bjorn Ekwall (bj0rn@blox.se)\n";

/*
 *	Based on adapter information gathered from DE600.ASM by D-Link Inc.,
 *	as included on disk C in the v.2.11 of PC/TCP from FTP Software.
 *
 *	For DE600.asm:
 *		Portions (C) Copyright 1990 D-Link, Inc.
 *		Copyright, 1988-1992, Russell Nelson, Crynwr Software
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2, or (at your option)
 *	any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <netinet/in.h>
#include <linux/ptrace.h>
#include <asm/system.h>
#include <errno.h>

#include "inet.h"
#include "dev.h"
#include "eth.h"
#include "timer.h"
#include "ip.h"
#include "route.h"
#include "protocol.h"
#include "tcp.h"
#include "skbuff.h"
#include "sock.h"
#include "arp.h"


/* use 0 for production, 1 for verification, >2 for debug */
#ifndef D_LINK_DEBUG
#define D_LINK_DEBUG 0
#endif
static unsigned int d_link_debug = D_LINK_DEBUG;

#ifdef D_LINK_DEBUG
#define PRINTK(x) if (d_link_debug >= 2) printk x
#else
#define PRINTK(x) /**/
#endif

/**************************************************
 *                                                *
 * Definition of D-Link Ethernet Pocket adapter   *
 *                                                *
 **************************************************/
/*
 * D-Link Ethernet pocket adapter ports
 */
#define DATA_PORT	(dev->base_addr + 0)
#define STATUS_PORT	(dev->base_addr + 1)
#define COMMAND_PORT	(dev->base_addr + 2)

/*
 * D-Link COMMAND_PORT commands
 */
#define SELECT_NIC	0x04 /* select Network Interface Card */
#define SELECT_PRN	0x1c /* select Printer */
#define NML_PRN		0xec /* normal Printer situation */
#define IRQEN		0x10 /* enable IRQ line */

/*
 * D-Link STATUS_PORT
 */
#define TX_INTR		0x88
#define RX_INTR		0x40

#define OTHER_INTR	0x00 /* dummy, always false */

/*
 * D-Link DATA_PORT commands
 * command in low 4 bits
 * data in high 4 bits
 * select current data nibble with HI_NIBBLE bit
 */
#define WRITE_DATA	0x00 /* write memory */
#define READ_DATA	0x01 /* read memory */
#define STATUS		0x02 /* read  status register */
#define COMMAND		0x03 /* write command register (see COMMAND below) */
#define NULL_COMMAND	0x04 /* null command */
#define RX_LEN		0x05 /* read  received packet length */
#define TX_ADDR		0x06 /* set adapter transmit memory address */
#define RW_ADDR		0x07 /* set adapter read/write memory address */
#define HI_NIBBLE	0x08 /* read/write the high nibble of data,
				or-ed with rest of command */

/*
 * command register, (I don't know all about these bits...)
 * accessed through DATA_PORT with low bits = COMMAND
 */
#define RX_ALL		0x01 /* bit 0,1 = 01 */
#define RX_BP		0x02 /* bit 0,1 = 10 */
#define RX_MBP		0x03 /* bit 0,1 = 11 */

#define TX_ENABLE	0x04 /* bit 2 */
#define RX_ENABLE	0x08 /* bit 3 */

#define RESET		0x80 /* set bit 7 high */
#define STOP_RESET	0x00 /* set bit 7 low */

/*
 * data to command register
 * (high 4 bits in write to DATA_PORT)
 */
#define RX_PAGE2_SELECT	0x10 /* bit 4, only 2 pages to select */
#define RX_BASE_PAGE	0x20 /* bit 5, always set when specifying RX_ADDR */
#define FLIP_IRQ	0x40 /* bit 6 */

/* Convenience definition, transmitter page 2 */
#define TX_PAGE2_SELECT	0x02

/*
 * D-Link adapter internal memory:
 *
 * 0-2K 1:st transmit page (send from pointer up to 2K)
 * 2-4K	2:nd transmit page (send from pointer up to 4K)
 *
 * 4-6K 1:st receive page (data from 4K upwards)
 * 6-8K 2:nd receive page (data from 6K upwards)
 *
 * 8K+	Adapter ROM (contains magic code and last 3 bytes of Ethernet address)
 */
#define MEM_2K		0x0800 /* 2048 */
#define MEM_4K		0x1000 /* 4096 */
#define NODE_ADDRESS	0x2000 /* 8192 */

#define RUNT 64 /*56*/ /* Too small Ethernet packet */

/**************************************************
 *                                                *
 *             End of definition                  *
 *                                                *
 **************************************************/

/* Common network statistics -- these will be in *.h someday. */
struct netstats {
	int	tx_packets;
	int	rx_packets;
	int	tx_errors;
	int	rx_errors;
	int	missed_packets;
	int	soft_tx_errors;
	int	soft_rx_errors;
	int	soft_trx_err_bits;
};
static struct netstats	*localstats;

/*
 * Index to functions, as function prototypes.
 */

/* Routines used internally. (See "convenience macros" also) */
static int		d_link_read_status(struct device *dev);
static unsigned	char	d_link_read_byte(int type, struct device *dev);

/* Put in the device structure. */
static int	d_link_open(struct device *dev);
static int	d_link_close(struct device *dev);
static int	d_link_start_xmit(struct sk_buff *skb, struct device *dev);

/* Dispatch from interrupts. */
static void	d_link_interrupt(int reg_ptr);
static void	d_link_tx_intr(struct device *dev);
static void	d_link_rx_intr(struct device *dev);

/* Initialization */
int		d_link_init(struct device *dev);
static void	adapter_init(struct device *dev, int startp);

/* Passed to sigaction() to register the interrupt handler. */
static struct sigaction d_link_sigaction = {
	&d_link_interrupt,
	0,
	0,
	NULL,
	};

/*
 * D-Link driver variables:
 */
static volatile int		rx_page		= 0;
static struct device		*realdev;
#ifdef D_LINK_FIFO
static volatile int		free_tx_page = 0x03;	/* 2 pages = 0000 0011 */
static volatile unsigned int	busy_tx_page = 0x00;	/* 2 pages = 0000 0000 */
static volatile int		transmit_next_from;
#endif

/*
 * Convenience macros/functions for D-Link adapter
 *
 * If you are having trouble reading/writing correctly,
 * try to uncomment the line "#define REALLY_SLOW_IO" (near line 20)
 */

#define select_prn() \
	outb_p(SELECT_PRN, COMMAND_PORT)

#define select_nic() \
	outb_p(SELECT_NIC, COMMAND_PORT)

#define d_link_put_byte(data) \
	outb_p(((data) << 4)   | WRITE_DATA            , DATA_PORT); \
	outb_p(((data) & 0xf0) | WRITE_DATA | HI_NIBBLE, DATA_PORT)

/*
 * The first two outb_p()'s below could perhaps be deleted if there
 * would be more delay in the last two. Not certain about it yet...
 */
#define d_link_put_command(cmd) \
	outb_p(( rx_page        << 4)   | COMMAND            , DATA_PORT); \
	outb_p(( rx_page        & 0xf0) | COMMAND | HI_NIBBLE, DATA_PORT); \
	outb_p(((rx_page | cmd) << 4)   | COMMAND            , DATA_PORT); \
	outb_p(((rx_page | cmd) & 0xf0) | COMMAND | HI_NIBBLE, DATA_PORT)

#define d_link_setup_address(addr,type) \
	outb_p((((addr) << 4) & 0xf0) | type            , DATA_PORT); \
	outb_p(( (addr)       & 0xf0) | type | HI_NIBBLE, DATA_PORT); \
	outb_p((((addr) >> 4) & 0xf0) | type            , DATA_PORT); \
	outb_p((((addr) >> 8) & 0xf0) | type | HI_NIBBLE, DATA_PORT)

static int
d_link_read_status(struct device *dev)
{
	int	status;

	select_nic();
	outb_p(STATUS, DATA_PORT);
	SLOW_DOWN_IO; /* See comment line 20 */
	status = inb_p(STATUS_PORT);
	outb_p(NULL_COMMAND, DATA_PORT);
	outb_p(NULL_COMMAND | HI_NIBBLE, DATA_PORT);

	return status;
}

static unsigned char
d_link_read_byte(int type, struct device *dev) { /* dev used by macros */
	unsigned char	lo;

	outb_p((type), DATA_PORT);
	SLOW_DOWN_IO; /* See comment line 20 */
	lo = inb_p(STATUS_PORT);
	outb_p((type) | HI_NIBBLE, DATA_PORT);
	SLOW_DOWN_IO; /* See comment line 20 */
	return ((lo & 0xf0) >> 4) | (inb_p(STATUS_PORT) & 0xf0);
}

#ifdef D_LINK_FIFO
	/* Handle a "fifo" in an int (= busy_tx_page) */
# define AT_FIFO_OUTPUT (busy_tx_page & 0x0f)
# define ANY_QUEUED_IN_FIFO (busy_tx_page & 0xf0)

# define PULL_FROM_FIFO	{ busy_tx_page >>= 4;}
# define PUSH_INTO_FIFO(page) { \
	if (busy_tx_page)	/* there already is a transmit in progress */ \
		busy_tx_page |= (page << 4); \
	else \
		busy_tx_page = page; \
	}
#endif

/*
 * Open/initialize the board.  This is called (in the current kernel)
 * sometime after booting when the 'config <dev->name>' program is run.
 *
 * This routine should set everything up anew at each open, even
 * registers that "should" only need to be set once at boot, so that
 * there is a non-reboot way to recover if something goes wrong.
 */
static int
d_link_open(struct device *dev)
{
	adapter_init(dev, 1);
	dev->tbusy = 0;		/* Transmit busy...  */
	dev->interrupt = 0;
	dev->start = 1;
	return 0;
}

/*
 * The inverse routine to d_link_open().
 */
static int
d_link_close(struct device *dev)
{
	dev->start = 0;

	adapter_init(dev, 0);

	irqaction(dev->irq, NULL);

	return 0;
}

/*
 * Copy a buffer to the adapter transmit page memory.
 * Start sending.
 */
static int
d_link_start_xmit(struct sk_buff *skb, struct device *dev)
{
	static int	tx_page = 0;
	int		transmit_from;
	int		len;
	int		tickssofar;
	unsigned char	*buffer = (unsigned char *)(skb + 1);

	/*
	 * If some higher layer thinks we've missed a
	 * tx-done interrupt * we are passed NULL.
	 * Caution: dev_tint() handles the cli()/sti() itself.
	 */

	if (skb == NULL) {
		dev_tint(dev);
		return 0;
	}

	/* For ethernet, fill in the header (hardware addresses) with an arp. */
	if (!skb->arp  &&  dev->rebuild_header(skb + 1, dev)) {
		skb->dev = dev;
		arp_queue (skb);
		return 0;
	}

#ifdef D_LINK_FIFO
	if (free_tx_page == 0) {	/* Do timeouts, to avoid hangs. */
#else
	if (dev->tbusy) {	/* Do timeouts, to avoid hangs. */
#endif
		tickssofar = jiffies - dev->trans_start;

		if (tickssofar < 5) {
			return 1;
		}

		/* else */
		printk("%s: transmit timed out (%d), %s?\n",
			dev->name,
			tickssofar,
			"network cable problem"
			);
		/* Try to restart the adapter. */
		/* Maybe in next release... :-)
		adapter_init(dev, 1);
		*/
	}

	/* Start real output */
	PRINTK(("d_link_start_xmit:len=%d\n", skb->len));
	cli();
	select_nic();

#ifdef D_LINK_FIFO
	/* magic code selects the least significant bit in free_tx_page */
	tx_page = free_tx_page & (-free_tx_page);

	free_tx_page &= ~tx_page;
	dev->tbusy = !free_tx_page; /* any more free pages? */

	PUSH_INTO_FIFO(tx_page);
#else
	tx_page ^= TX_PAGE2_SELECT;	/* Flip page, only 2 pages */
#endif

	if ((len = skb->len) < RUNT) /*&& Hmm...? */
		len = RUNT;

	if (tx_page & TX_PAGE2_SELECT)
		transmit_from = MEM_4K - len;
	else
		transmit_from = MEM_2K - len;

	d_link_setup_address(transmit_from, RW_ADDR);

	for ( ; len > 0; --len, ++buffer) {
		d_link_put_byte(*buffer); /* macro! watch out for side effects! */
	}

#ifdef D_LINK_FIFO
	if (ANY_QUEUED_IN_FIFO == 0)	{ /* there is no transmit in progress */
		d_link_setup_address(transmit_from, TX_ADDR);
		d_link_put_command(TX_ENABLE);
		dev->trans_start = jiffies;
	} else {
		transmit_next_from = transmit_from;
	}
#else
	d_link_setup_address(transmit_from, TX_ADDR);
	d_link_put_command(TX_ENABLE);
	dev->trans_start = jiffies;
#endif

	sti(); /* interrupts back on */
	
	if (skb->free) {
		kfree_skb (skb, FREE_WRITE);
	}

	return 0;
}

/*
 * The typical workload of the driver:
 * Handle the network interface interrupts.
 */
static void
d_link_interrupt(int reg_ptr)
{
	int		irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2);
	struct device	*dev = realdev;
	int		interrupts;

	/* Get corresponding device */
/*
	for (dev = dev_base; dev != NULL; dev = dev->next) {
		if (dev->irq == irq)
			break;
	}
*/
	if (dev == NULL) {
		printk ("d_link_interrupt(): irq %d for unknown device.\n", irq);
		return;
	}

	if (dev->start == 0) {
		return; /*&& bogus interrupt at boot!?!?!? */
	}

	cli();
	dev->interrupt = 1;
	sti();	/* Allow other interrupts. */

	localstats = (struct netstats*) dev->priv;

	interrupts = d_link_read_status(dev);

	PRINTK(("d_link_interrupt (%2.2X)\n", interrupts));

	/*
	 * Interrupts have been observed to be:
	 *
	 * Value	My interpretation
	 * -----	-----------------
	 * 0x47		Normal receive interrupt
	 * 0x4F		Receive AND transmit interrupt simultaneously
	 * 0x87		Normal transmit interrupt (? I treat it as such...)
	 * 0x8F		Normal transmit interrupt
	 */

	/*
	 * Take care of TX interrupts first, in case there is an extra
         * page to transmit (keep the adapter busy while we work).
	 */
	if (interrupts & TX_INTR) { /* 1xxx 1xxx */
		d_link_tx_intr(dev);
	}

	if (interrupts & RX_INTR) { /* x1xx xxxx */
		d_link_rx_intr(dev);
	}

	/* I'm not sure if there are any other interrupts from D-Link... */
	if (d_link_debug && (interrupts & OTHER_INTR)) {
		printk("%s: unknown interrupt %#2x\n", dev->name, interrupts);
	}

	/* Check comment near line 20! */
	if (	(interrupts != 0x47) &&
		(interrupts != 0x87) &&
		(interrupts != 0x4F) &&
		(interrupts != 0x8F)
		)
		printk("Strange d_link_interrupt: <%2.2X>\n", interrupts);

	cli();
	dev->interrupt = 0;

	/* Enable adapter interrupts */
	select_prn();
	return;
}

/*
 * Do internal handshake: Transmitter done (of this page).
 * Also handle the case of a pending transmit page.
 */
static void
d_link_tx_intr(struct device *dev)
{
	localstats->tx_packets++;

	cli();
	dev->tbusy = 0;

#ifdef D_LINK_FIFO
	free_tx_page |= AT_FIFO_OUTPUT;
	PULL_FROM_FIFO;

	if (AT_FIFO_OUTPUT != 0) { /* more in queue! */
		d_link_setup_address(transmit_next_from, TX_ADDR);
		d_link_put_command(TX_ENABLE);
		dev->trans_start = jiffies;
	}
#endif

	mark_bh(INET_BH);
	sti();
}

/*
 * We have a good packet(s), get it/them out of the buffers.
 */
static void
d_link_rx_intr(struct device *dev)
{
	struct sk_buff	*skb;
	int		i;
	int		size;
	int		sksize;
	unsigned char	*buffer;

	/* Get size of received packet */
	/* Ignore trailing 4 CRC-bytes */
	size = d_link_read_byte(RX_LEN, dev); /* low byte */
	size = size + (d_link_read_byte(RX_LEN, dev) << 8) - 4;

	/* Tell adapter where to store next incoming packet, enable receiver */
	rx_page ^= RX_PAGE2_SELECT;	/* Flip bit, only 2 pages */
	d_link_put_command(RX_ENABLE);

	if (size == 0)		/* Read all the frames? */
		return;			/* Done for now */

	if ((size < 32  ||  size > 1535) && d_link_debug)
		printk("%s: Bogus packet size %d.\n", dev->name, size);

	sksize = sizeof(struct sk_buff) + size;
	if ((skb = (struct sk_buff *) kmalloc(sksize, GFP_ATOMIC)) == NULL) {
		if (d_link_debug) {
				printk("%s: Couldn't allocate a sk_buff of size %d.\n",
					dev->name, sksize);
		}
		return;
	}
	/* else */

	skb->lock = 0;
	skb->mem_len = sksize;
	skb->mem_addr = skb;
	/* 'skb + 1' points to the start of sk_buff data area. */
	buffer = (unsigned char *)(skb + 1);

	/* Get packet */

	/* Tell adapter from where we want to read this packet */
	if (rx_page & RX_PAGE2_SELECT) {
		d_link_setup_address(MEM_4K, RW_ADDR);
	} else {
		d_link_setup_address(MEM_4K + MEM_2K, RW_ADDR);
	}

	/* copy the packet into the buffer */
	for (i = size; i > 0; --i) {
		*buffer++ = d_link_read_byte(READ_DATA, dev);
	}
	
	localstats->rx_packets++; /* count all receives */

	if(dev_rint((unsigned char *)skb, size, IN_SKBUFF, dev)) {
		printk("%s: receive buffers full.\n", dev->name);
		return;
	}

	/*
	 * If any worth-while packets have been received, dev_rint()
	 * has done a mark_bh(INET_BH) for us and will work on them
	 * when we get to the bottom-half routine.
	 */
	return;
}

int
d_link_init(struct device *dev)
{
	int	i;

	printk("%s: D-Link pocket adapter", dev->name);
	/* Alpha testers must have the version number to report bugs. */
	if (d_link_debug > 1)
		printk(version);

	/* probe for adapter */
	rx_page = 0;
	(void)d_link_read_status(dev);
	d_link_put_command(RESET);
	d_link_put_command(STOP_RESET);
	if (d_link_read_status(dev) & 0xf0) {
		printk(": probe failed at %#3x.\n", dev->base_addr);
		return ENODEV;
	}

	/*
	 * Maybe we found one,
	 * have to check if it is a D-Link adapter...
	 */

	/* Get the adapter ethernet address from the ROM */
	d_link_setup_address(NODE_ADDRESS, RW_ADDR);
	for (i = 0; i < ETH_ALEN; i++) {
		dev->dev_addr[i] = d_link_read_byte(READ_DATA, dev);
		dev->broadcast[i] = 0xff;
	}

	/* Check magic code */
	if ((dev->dev_addr[1] == 0xde) && (dev->dev_addr[2] == 0x15)) {
		/* OK, install real address */
		dev->dev_addr[0] = 0x00;
		dev->dev_addr[1] = 0x80;
		dev->dev_addr[2] = 0xc8;
		dev->dev_addr[3] &= 0x0f;
		dev->dev_addr[3] |= 0x70;
	} else {
		printk(", not found in printer port!\n");
		return ENODEV;
	}

	/* Initialize the device structure. */
	dev->priv = kmalloc(sizeof(struct netstats), GFP_KERNEL);
	memset(dev->priv, 0, sizeof(struct netstats));

	for (i = 0; i < DEV_NUMBUFFS; i++)
		dev->buffs[i] = NULL;

	dev->hard_header = eth_header;
	dev->add_arp = eth_add_arp;
	dev->queue_xmit = dev_queue_xmit;
	dev->rebuild_header = eth_rebuild_header;
	dev->type_trans = eth_type_trans;

	dev->open = &d_link_open;
	dev->stop = &d_link_close;
	dev->hard_start_xmit = &d_link_start_xmit;

	/* These are ethernet specific. */
	dev->type = ARPHRD_ETHER;
	dev->hard_header_len = ETH_HLEN;
	dev->mtu = 1500; /* eth_mtu */
	dev->addr_len	= ETH_ALEN;

	/* New-style flags. */
	dev->flags = IFF_BROADCAST;
	dev->family = AF_INET;
	dev->pa_addr = 0;
	dev->pa_brdaddr = 0;
	dev->pa_mask = 0;
	dev->pa_alen = sizeof(unsigned long);

	if (irqaction (dev->irq, &d_link_sigaction)) {
		printk (": unable to get IRQ %d\n", dev->irq);
		return 0;
	}

	printk(", Ethernet Address: %2.2X", dev->dev_addr[0]);
	for (i = 1; i < ETH_ALEN; i++)
		printk(":%2.2X",dev->dev_addr[i]);
	printk("\n");

	return 0;
}

static void
adapter_init(struct device *dev, int startp)
{
	int	i;

	cli(); /* no interrupts yet, please */

	select_nic();
	rx_page = 0;
	d_link_put_command(RESET);
	d_link_put_command(STOP_RESET);

	if (startp) {
		irqaction (dev->irq, &d_link_sigaction);
		realdev = dev;
#ifdef D_LINK_FIFO
		free_tx_page = 0x03;	/* 2 pages = 0000 0011 */
		busy_tx_page = 0x00;	/* 2 pages = 0000 0000 */
#endif
		/* set the ether address. */
		d_link_setup_address(NODE_ADDRESS, RW_ADDR);
		for (i = 0; i < ETH_ALEN; i++) {
			d_link_put_byte(dev->dev_addr[i]);
		}

		/* where to start saving incoming packets */
		rx_page = 0 | RX_BP | RX_BASE_PAGE;
		d_link_setup_address(MEM_4K, RW_ADDR);
		/* Enable receiver */
		d_link_put_command(RX_ENABLE);
	}
	else
		d_link_put_command(0);

	select_prn();

	sti();
}
