⛏️ index : haiku.git

/*
 * Copyright (c) 2003-2004 Stefano Ceccherini (burton666@libero.it)
 * Copyright (c) 1997, 1998
 *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Bill Paul.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "wb840.h"
#include "device.h"
#include "interface.h"

#include <ByteOrder.h>
#include <KernelExport.h>

#include <string.h>


#define SIO_SET(x)	\
	write32(device->reg_base + WB_SIO,	\
		read32(device->reg_base + WB_SIO) | x)

#define SIO_CLR(x)	\
	write32(device->reg_base + WB_SIO,	\
		read32(device->reg_base + WB_SIO) & ~x)

#define MII_DELAY(x)	read32(x->reg_base + WB_SIO)


static void
mii_sync(struct wb_device *device)
{
	// Set data bit and strobe the clock 32 times
	int bits = 32;

	SIO_SET(WB_SIO_MII_DIR|WB_SIO_MII_DATAIN);

	while (--bits >= 0) {
		SIO_SET(WB_SIO_MII_CLK);
		MII_DELAY(device);
		SIO_CLR(WB_SIO_MII_CLK);
		MII_DELAY(device);
	}
}


static void
mii_send(wb_device *device, uint32 bits, int count)
{
	int	i;

	SIO_CLR(WB_SIO_MII_CLK);

	for (i = (0x1 << (count - 1)); i; i >>= 1) {
		if (bits & i)
			SIO_SET(WB_SIO_MII_DATAIN);
		else
			SIO_CLR(WB_SIO_MII_DATAIN);
		MII_DELAY(device);
		SIO_CLR(WB_SIO_MII_CLK);
		MII_DELAY(device);
		SIO_SET(WB_SIO_MII_CLK);
	}
}

/*
 * Read an PHY register through the MII.
 */
static int
wb_mii_readreg(wb_device *device, wb_mii_frame *frame)
{
	int	i, ack;

	/*
	 * Set up frame for RX.
	 */
	frame->mii_stdelim = WB_MII_STARTDELIM;
	frame->mii_opcode = WB_MII_READOP;
	frame->mii_turnaround = 0;
	frame->mii_data = 0;

	write32(device->reg_base + WB_SIO, 0);

	/*
 	 * Turn on data xmit.
	 */
	SIO_SET(WB_SIO_MII_DIR);

	mii_sync(device);

	/*
	 * Send command/address info.
	 */
	mii_send(device, frame->mii_stdelim, 2);
	mii_send(device, frame->mii_opcode, 2);
	mii_send(device, frame->mii_phyaddr, 5);
	mii_send(device, frame->mii_regaddr, 5);

	/* Idle bit */
	SIO_CLR((WB_SIO_MII_CLK|WB_SIO_MII_DATAIN));
	MII_DELAY(device);
	SIO_SET(WB_SIO_MII_CLK);
	MII_DELAY(device);

	/* Turn off xmit. */
	SIO_CLR(WB_SIO_MII_DIR);
	/* Check for ack */
	SIO_CLR(WB_SIO_MII_CLK);
	MII_DELAY(device);
	ack = read32(device->reg_base + WB_SIO) & WB_SIO_MII_DATAOUT;
	SIO_SET(WB_SIO_MII_CLK);
	MII_DELAY(device);
	SIO_CLR(WB_SIO_MII_CLK);
	MII_DELAY(device);
	SIO_SET(WB_SIO_MII_CLK);
	MII_DELAY(device);

	/*
	 * Now try reading data bits. If the ack failed, we still
	 * need to clock through 16 cycles to keep the PHY(s) in sync.
	 */
	if (ack) {
		for(i = 0; i < 16; i++) {
			SIO_CLR(WB_SIO_MII_CLK);
			MII_DELAY(device);
			SIO_SET(WB_SIO_MII_CLK);
			MII_DELAY(device);
		}
		goto fail;
	}

	for (i = 0x8000; i; i >>= 1) {
		SIO_CLR(WB_SIO_MII_CLK);
		MII_DELAY(device);
		if (!ack) {
			if (read32(device->reg_base + WB_SIO) & WB_SIO_MII_DATAOUT)
				frame->mii_data |= i;
			MII_DELAY(device);
		}
		SIO_SET(WB_SIO_MII_CLK);
		MII_DELAY(device);
	}

fail:

	SIO_CLR(WB_SIO_MII_CLK);
	MII_DELAY(device);
	SIO_SET(WB_SIO_MII_CLK);
	MII_DELAY(device);

	if (ack)
		return 1;
	return 0;
}

/*
 * Write to a PHY register through the MII.
 */
static int
wb_mii_writereg(wb_device *device, wb_mii_frame	*frame)
{
	/*
	 * Set up frame for TX.
	 */

	frame->mii_stdelim = WB_MII_STARTDELIM;
	frame->mii_opcode = WB_MII_WRITEOP;
	frame->mii_turnaround = WB_MII_TURNAROUND;

	/*
 	 * Turn on data output.
	 */
	SIO_SET(WB_SIO_MII_DIR);

	mii_sync(device);

	mii_send(device, frame->mii_stdelim, 2);
	mii_send(device, frame->mii_opcode, 2);
	mii_send(device, frame->mii_phyaddr, 5);
	mii_send(device, frame->mii_regaddr, 5);
	mii_send(device, frame->mii_turnaround, 2);
	mii_send(device, frame->mii_data, 16);

	/* Idle bit. */
	SIO_SET(WB_SIO_MII_CLK);
	MII_DELAY(device);
	SIO_CLR(WB_SIO_MII_CLK);
	MII_DELAY(device);

	/*
	 * Turn off xmit.
	 */
	SIO_CLR(WB_SIO_MII_DIR);

	return 0;
}


int
wb_miibus_readreg(wb_device *device, int phy, int reg)
{
	struct wb_mii_frame frame;

	memset(&frame, 0, sizeof(frame));

	frame.mii_phyaddr = phy;
	frame.mii_regaddr = reg;
	wb_mii_readreg(device, &frame);

	return frame.mii_data;
}


void
wb_miibus_writereg(wb_device *device, int phy, int reg, int data)
{
	struct wb_mii_frame frame;

	memset(&frame, 0, sizeof(frame));

	frame.mii_phyaddr = phy;
	frame.mii_regaddr = reg;
	frame.mii_data = data;

	wb_mii_writereg(device, &frame);

	return;
}


#define EEPROM_DELAY(x)	read32(x->reg_base + WB_SIO)

#if 0
static void
wb_eeprom_putbyte(wb_device *device, int addr)
{
	int	d, i;
	int delay;

	d = addr | WB_EECMD_READ;

	/*
	 * Feed in each bit and strobe the clock.
	 */
	for (i = 0x400; i; i >>= 1) {
		if (d & i) {
			SIO_SET(WB_SIO_EE_DATAIN);
		} else {
			SIO_CLR(WB_SIO_EE_DATAIN);
		}
		for (delay = 0; delay < 100; delay++)
			MII_DELAY(device);

		SIO_SET(WB_SIO_EE_CLK);

		for (delay = 0; delay < 150; delay++)
			MII_DELAY(device);

		SIO_CLR(WB_SIO_EE_CLK);

		for (delay = 0; delay < 100; delay++)
			MII_DELAY(device);

	}

	return;
}
#endif


static void
wb_eeprom_askdata(wb_device *device, int addr)
{
	int command, i;
	int delay;

	command = addr | WB_EECMD_READ;

	/* Feed in each bit and strobe the clock. */
	for(i = 0x400; i; i >>= 1) {
		if (command & i)
			SIO_SET(WB_SIO_EE_DATAIN);
		else
			SIO_CLR(WB_SIO_EE_DATAIN);

		SIO_SET(WB_SIO_EE_CLK);

		SIO_CLR(WB_SIO_EE_CLK);
		for (delay = 0; delay < 100; delay++)
			EEPROM_DELAY(device);
	}
}


/* Read a word of data stored in the EEPROM at address "addr". */
static void
wb_eeprom_getword(wb_device *device, int addr, uint16 *dest)
{
	int i;
	uint16 word = 0;

	/* Enter EEPROM access mode */
	write32(device->reg_base + WB_SIO, WB_SIO_EESEL|WB_SIO_EE_CS);

	/* Send address of word we want to read. */
	wb_eeprom_askdata(device, addr);

	write32(device->reg_base + WB_SIO, WB_SIO_EESEL|WB_SIO_EE_CS);

	/* Start reading bits from EEPROM */
	for (i = 0x8000; i > 0; i >>= 1) {
		SIO_SET(WB_SIO_EE_CLK);
		if (read32(device->reg_base + WB_SIO) & WB_SIO_EE_DATAOUT)
			word |= i;
		SIO_CLR(WB_SIO_EE_CLK);
	}

	/* Turn off EEPROM access mode */
	write32(device->reg_base + WB_SIO, 0);

	*dest = word;
}


void
wb_read_eeprom(wb_device *device, void* dest,
	int offset, int count, bool swap)
{
	int i;
	uint16 word = 0, *ptr;

	for (i = 0; i < count; i++) {
		wb_eeprom_getword(device, offset + i, &word);
		ptr = (uint16 *)((uint8 *)dest + (i * 2));
		if (swap)
			*ptr = B_BENDIAN_TO_HOST_INT16(word);
		else
			*ptr = word;
	}
}