/*
 * (C) Copyright 2006
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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 of
 * the License, 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., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <mpc5xxx.h>

/* For the V38B board the pin is GPIO_PSC_6 */
#define GPIO_PIN	GPIO_PSC6_0

#define NO_ERROR	0
#define ERR_NO_NUMBER	1
#define ERR_BAD_NUMBER	2

static int is_high(void);
static int check_device(void);
static void io_out(int value);
static void io_input(void);
static void io_output(void);
static void init_gpio(void);
static void read_byte(unsigned char *data);
static void write_byte(unsigned char command);

void read_2501_memory(unsigned char *psernum, unsigned char *perr);
void board_get_enetaddr(uchar *enetaddr);


static int is_high()
{
	return (*((vu_long *) MPC5XXX_WU_GPIO_DATA_I) & GPIO_PIN);
}

static void io_out(int value)
{
	if (value)
		*((vu_long *) MPC5XXX_WU_GPIO_DATA_O) |= GPIO_PIN;
	else
		*((vu_long *) MPC5XXX_WU_GPIO_DATA_O) &= ~GPIO_PIN;
}

static void io_input()
{
	*((vu_long *) MPC5XXX_WU_GPIO_DIR) &= ~GPIO_PIN;
	udelay(3);	/* allow input to settle */
}

static void io_output()
{
	*((vu_long *) MPC5XXX_WU_GPIO_DIR) |= GPIO_PIN;
}

static void init_gpio()
{
	*((vu_long *) MPC5XXX_WU_GPIO_ENABLE) |= GPIO_PIN;	/* Enable appropriate pin */
}

void read_2501_memory(unsigned char *psernum, unsigned char *perr)
{
#define NBYTES 28
	unsigned char crcval, i;
	unsigned char buf[NBYTES];

	*perr = 0;
	crcval = 0;

	for (i = 0; i < NBYTES; i++)
		buf[i] = 0;

	if (!check_device())
		*perr = ERR_NO_NUMBER;
	else {
		*perr = NO_ERROR;
		write_byte(0xCC);		/* skip ROM (0xCC) */
		write_byte(0xF0);		/* Read memory command 0xF0 */
		write_byte(0x00);		/* Address TA1=0, TA2=0 */
		write_byte(0x00);
		read_byte(&crcval);		/* Read CRC of address and command */

		for (i = 0; i < NBYTES; i++)
			read_byte(&buf[i]);
	}
	if (strncmp((const char *) &buf[11], "MAREL IEEE 802.3", 16)) {
		*perr = ERR_BAD_NUMBER;
		psernum[0] = 0x00;
		psernum[1] = 0xE0;
		psernum[2] = 0xEE;
		psernum[3] = 0xFF;
		psernum[4] = 0xFF;
		psernum[5] = 0xFF;
	} else {
		psernum[0] = 0x00;
		psernum[1] = 0xE0;
		psernum[2] = 0xEE;
		psernum[3] = buf[7];
		psernum[4] = buf[6];
		psernum[5] = buf[5];
	}
}

static int check_device()
{
	int found;

	io_output();
	io_out(0);
	udelay(500);  /* must be at least 480 us low pulse */

	io_input();
	udelay(60);

	found = (is_high() == 0) ? 1 : 0;
	udelay(500);  /* must be at least 480 us low pulse */

	return found;
}

static void write_byte(unsigned char command)
{
	char i;

	for (i = 0; i < 8; i++) {
		/* 1 us to 15 us low pulse starts bit slot */
		/* Start with high pulse for 3 us */
		io_input();
		udelay(3);

		io_out(0);
		io_output();
		udelay(3);

		if (command & 0x01) {
			/* 60 us high for 1-bit */
			io_input();
			udelay(60);
		} else
			/* 60 us low for 0-bit */
			udelay(60);
		/*  Leave pin as input */
		io_input();

		command = command >> 1;
	}
}

static void read_byte(unsigned char *data)
{
	unsigned char i, rdat = 0;

	for (i = 0; i < 8; i++) {
		/* read one bit from one-wire device */

		/* 1 - 15 us low starts bit slot */
		io_out(0);
		io_output();
		udelay(0);

		/* allow line to be pulled high */
		io_input();

		/* delay 10 us */
		udelay(10);

		/* now sample input status */
		if (is_high())
			rdat = (rdat >> 1) | 0x80;
		else
			rdat = rdat >> 1;

		udelay(60);	/* at least 60 us */
	}
	/* copy the return value */
	*data = rdat;
}

void board_get_enetaddr(uchar *enetaddr)
{
	unsigned char sn[6], err = NO_ERROR;

	init_gpio();

	read_2501_memory(sn, &err);

	if (err == NO_ERROR) {
		sprintf((char *)enetaddr, "%02x:%02x:%02x:%02x:%02x:%02x",
				sn[0], sn[1], sn[2], sn[3], sn[4], sn[5]);
		printf("MAC address: %s\n", enetaddr);
		setenv("ethaddr", (char *)enetaddr);
	} else {
		sprintf((char *)enetaddr, "00:01:02:03:04:05");
		printf("Error reading MAC address.\n");
		printf("Setting default to %s\n", enetaddr);
		setenv("ethaddr", (char *)enetaddr);
	}
}