/*======================================================================

    Utility to select the transceiver type for some D-Link (and also
    Linksys) ethernet adapters

    Compile with: cc -O2 -o dlport dlport.c
    
    Written by David Hinds, dhinds@hyper.stanford.edu

    dlport.c 1.3 1997/04/30 05:10:58
    
======================================================================*/

#ifdef __KERNEL__

#include <linux/types.h>
#include <linux/delay.h>
#include <asm/io.h>

#else

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <asm/io.h>

#define udelay(n)	usleep(n)

char *if_names[] = { "10base2", "10baseT" };

#endif

/*====================================================================*/

#define ioaddr_t	u_short
#define OP_ADRCNT	11
#define OP_READ		0x0600
#define OP_WRITE	0x0500
#define OP_EWEN		0x04c0
#define OP_ERASE	0x0700
#define OP_EWDS		0x0400

/* Mapping of EEPROM pins to register bits */
#define E_REFCLK	0x80
#define E_EEROM		0x40
#define E_RECALL	0x20
#define E_ASIC		0x10
#define E_CS		0x08
#define E_CLK		0x04
#define E_DIN		0x02
#define E_DOUT		0x01

static void slow_12_8us(ioaddr_t port)
{
    u_char a, b, i;
    for (i = b = 0; (i < 32) && (b < 3); i++) {
	inb(port); inb(port);
	a = inb(port) & E_REFCLK;
	if ((b & 1) ? (a) : (!a)) b++;
    }
#ifndef __KERNEL__
    if (i == 16) printf("slow_12_8us() timed out!\n");
#endif
}

static void send_list(const u_char *v, ioaddr_t port)
{
    u_char n;
    for (n = *v++; n; n--, v++) {
	outb(*v | E_EEROM, port);
	slow_12_8us(port);
    }
}

static void set_send_state(ioaddr_t port)
{
    static const u_char cmd[] =
    { 3, 0x01, 0x09, 0x0d };
    send_list(cmd, port);
}

static void reset_send_state(ioaddr_t port)
{
    static const u_char cmd[] =
    { 8, 0x09, 0x01, 0x05, 0x01, 0x05, 0x01, 0x05, 0x01 };
    send_list(cmd, port);
}

void send_bits(u_int v, u_char bits, ioaddr_t port)
{
    static const u_char send_0[] = { 2, 0x09, 0x0d };
    static const u_char send_1[] = { 2, 0x0b, 0x0f };
    u_short i;
    for (i = 1<<(bits-1); i != 0; i >>= 1)
	send_list(((v & i) ? send_1 : send_0), port);
}

static void simple_cmd(u_int cmd, ioaddr_t port)
{
    set_send_state(port);
    send_bits(cmd, OP_ADRCNT, port);
    reset_send_state(port);
}

static u_short read_eeprom(ioaddr_t port, u_short addr)
{
    u_short i, v;
    set_send_state(port);
    send_bits(OP_READ|addr, OP_ADRCNT, port);
    for (i = v = 0; i < 16; i++) {
	send_bits(0, 1, port);
	v = (v<<1) | (inb(port) & E_DOUT);
    }
    reset_send_state(port);
    return v;
}

static void write_wait(ioaddr_t port)
{
    int i;
    set_send_state(port);
    /* The datasheet is unclear... but 15ms should cover it */
    for (i = 0; i < 150; i++) {
	udelay(100);
	if (inb(port) & E_DOUT) break;
    }
#ifndef __KERNEL__
    if (i == 150) printf("write_wait() timed out!\n");
#endif
    reset_send_state(port);
}

static void write_eeprom(ioaddr_t port, u_short addr, u_short val)
{
    simple_cmd(OP_EWEN, port);
    simple_cmd(OP_ERASE|addr, port);
    write_wait(port);
    set_send_state(port);
    send_bits(OP_WRITE|addr, OP_ADRCNT, port);
    send_bits(val, 16, port);
    reset_send_state(port);
    write_wait(port);
    simple_cmd(OP_EWDS, port);
}

static inline void reload(ioaddr_t port)
{
    int i;
    static const u_char cmd[] = { 4, 0x00, 0x00, 0x20, 0x00 };
    send_list(cmd, port);
    for (i = 0; i < 200; i++) {
	udelay(100);
	if (!(inb(port) & E_EEROM)) break;
    }
#ifndef __KERNEL__
    if (i == 200) printf("reload() timed out!\n");
#endif
}

void change_media(ioaddr_t base, u_char if_port)
{
    u_short w;
    w = read_eeprom(base+0x1e, 4);
    /* BNC is 0, UTP is 1 */
    if ((w & 2) != (if_port << 1)) {
	w = (w & (~2)) | (if_port << 1);
	write_eeprom(base+0x1e, 4, w);
	reload(base+0x1e);
    }
}

/*====================================================================*/

#ifndef __KERNEL__
static int sockets_open(void)
{
    int sock;
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) != -1)
	return sock;
    else if ((sock = socket(AF_IPX, SOCK_DGRAM, 0)) != -1)
	return sock;
    else if ((sock = socket(AF_AX25, SOCK_DGRAM, 0)) != -1)
	return sock;
    else
	return socket(AF_APPLETALK, SOCK_DGRAM, 0);
}

void usage(char *s)
{
    fprintf(stderr, "usage: %s interface [10baseT|10base2]\n", s);
    exit(1);
}

void main(int argc, char **argv)
{
    struct ifreq ifr;
    int i, skfd;

    if ((argc < 2) || (argc > 3))
	usage(argv[0]);
    skfd = sockets_open();
    if (skfd == -1) {
	perror("socket");
	exit(1);
    }
    strcpy(ifr.ifr_name, argv[1]);
    if (ioctl(skfd, SIOCGIFMAP, &ifr) < 0)
	fprintf(stderr, "%s: unknown interface.\n", argv[1]);
    else {
	ioperm(0x80, 1, 1);
	ioperm(ifr.ifr_map.base_addr+0x1e, 1, 1);
	if (argc == 2) {
	    i = read_eeprom(ifr.ifr_map.base_addr+0x1e, 4);
	    printf("%s\t%s\n", argv[1], if_names[(i >> 1) & 1]);
	} else {
	    for (i = 0; i < 2; i++)
		if (strcasecmp(argv[2], if_names[i]) == 0)
		    break;
	    if (i < 2) {
		change_media(ifr.ifr_map.base_addr, i);
	    } else
		perror("ioctl");
	}
    }
    close(skfd);
    exit(0);
}
#endif
