/*
 *    Simple onewire library implementation
 *
 *    Copyright (C) 2006 Allied Telesis Labs
 *
 * Based on code by:
 * (c) by Alexander Neumann <alexander@bumpern.de>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License (either version 2 or
 * version 3) as published by the Free Software Foundation.
 *
 * 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.
 *
 * For more information on the GPL, please go to:
 * http://www.gnu.org/copyleft/gpl.html
 */
#include <common.h>
#include <w1.h>

/* The one and only master */
static struct w1_master *g_master = NULL;

/* The current bus */
int g_bus = -1;

/* pin macros - use imported code without many changes */
#define OW_CONFIG_INPUT() \
{ \
	if (g_master && g_master->config_input) \
		g_master->config_input(g_bus); \
}

#define OW_CONFIG_OUTPUT() \
{ \
	if (g_master && g_master->config_output) \
		g_master->config_output(g_bus); \
}

#define OW_PULLUP() \
{ \
	if (g_master && g_master->config_pullup) \
		g_master->config_pullup(g_bus); \
}

#define OW_LOW() \
{ \
	if (g_master && g_master->drive_low) \
		g_master->drive_low(g_bus); \
}

#define OW_HIGH() \
{ \
	if (g_master && g_master->drive_high) \
		g_master->drive_high(g_bus); \
}

#define OW_GET_INPUT() \
	((g_master && g_master->read_input) ? \
		g_master->read_input(g_bus) : 0)

/* register the one and only w1 master */
void w1_register_master(struct w1_master *master)
{
	/* Set the master */
	g_master = master;
}

int w1_get_bus_num(void)
{
	/* Return the number of busses */
	if (!g_master)
		return 0;
	return g_master->num_bus;
}

void w1_set_bus(int bus)
{
	/* Set the bus */
	g_bus = bus;
}

/* low-level functions */
uint8_t ow_reset(void)
{
	/* configure onewire pin as input */
	OW_CONFIG_INPUT();

	/* enable pullup */
	OW_PULLUP();

	/* pull bus low */
	OW_CONFIG_OUTPUT();
	OW_LOW();

	/* wait 480us ≤ tRSTL < 960us */
	udelay(720);

	/* release bus */
	OW_HIGH();
	OW_CONFIG_INPUT();

	/* wait 60us (maximal pause) + 30 us (half minimum pulse) */
	udelay(60 + 30);

	/* sample data */
	uint8_t data1 = OW_GET_INPUT();

	/* wait 390us */
	udelay(390);

	/* sample data again */
	uint8_t data2 = OW_GET_INPUT();

	/* if first sample is low and second sample is high, at least one device is
	 * attached to this bus */
	return !data1 && data2;
}

void w1_write_0(void)
{
	/* a write 0 timeslot is initiated by holding the data line low for
	 * approximately 80us */
	OW_LOW();
	udelay(80);
	OW_HIGH();
	udelay(10);
}

void w1_write_1(void)
{
	/* a write 1 timeslot is initiated by holding the data line low for
	 * approximately 4us, then restore the idle state and wait at least 80us */
	OW_LOW();
	udelay(4);
	OW_HIGH();
	udelay(80);
}

void w1_write(uint8_t value)
{
	if (value > 0)
		w1_write_1();
	else
		w1_write_0();
}

void w1_write_byte(uint8_t value)
{
	uint8_t i;

	OW_CONFIG_OUTPUT();
	for (i = 0; i < 8; i++) {
		w1_write(value & (1 << (i)));
	}
}

uint8_t w1_read(void)
{
	/* a read timeslot is sent by holding the data line low for
	 * 1us, then wait approximately 14us, then sample data and
	 * wait  */

	OW_CONFIG_OUTPUT();
	OW_LOW();

	udelay(1);

	OW_HIGH();
	OW_CONFIG_INPUT();

	udelay(14);

	/* sample data now */
	uint8_t data = OW_GET_INPUT();

	/* wait for remaining slot time */
	udelay(65);

	return data;
}

uint8_t w1_read_byte(void)
{
	uint8_t data = 0;
	uint8_t i;

	for (i = 0; i < 8; i++) {
		data |= (w1_read() << i);
	}

	return data;
}

/* DS2502 */
static uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data)
{
	uint8_t i;

	crc = crc ^ data;
	for (i = 0; i < 8; i++) {
		if (crc & 0x01)
			crc = (crc >> 1) ^ 0x8C;
		else
			crc >>= 1;
	}
	return crc;
}

int w1_eeprom_read(uint8_t * data, int offset, int length)
{
	uint8_t i;

	/* Clear the data before the read */
	memset(data, 0x00, length);

	/* reset the bus */
	if (!ow_reset())
		return -1;

	/* transmit command byte */
	w1_write_byte(OW_ROM_SKIP_ROM);

	/* transmit command byte */
	w1_write_byte(OW_FUNC_READ_DATA_CRC);

	/* transmit address */
	w1_write_byte(offset);
	w1_write_byte(0);

	/* read back crc sum of the command */
	uint8_t crc = w1_read_byte();

	/* check crc */
	uint8_t crc2 = 0;
	crc2 = _crc_ibutton_update(crc2, OW_FUNC_READ_DATA_CRC);
	crc2 = _crc_ibutton_update(crc2, offset);
	crc2 = _crc_ibutton_update(crc2, 0);
	if (crc != crc2) {
		printf("crc(0x%02x) != crc2(0x%02x)\n", crc, crc2);
		return -2;
	}

	/* read data */
	for (i = 0; i < length; i++)
		data[i] = w1_read_byte();

	return 0;
}

int do_w1(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
	int rcode = 0;
	uint start_bus, end_bus;
	unsigned char data[16];
	int bus, i;

	/* Check we have a bus master */
	if (!g_master) {
		printf("Error: no master registered\n");
		return rcode;
	}

	/* Parse arguments */
	if (argc < 1 || argc > 2) {
		printf("Usage: w1 [0-%d]\n", g_master->num_bus);
		return rcode;
	}

	/* Default to parsing every bus */
	start_bus = 0;
	end_bus = g_master->num_bus - 1;

	/* Check if the bus has been specified */
	if (argc == 2) {
		/* Parse the bus */
		start_bus = end_bus = simple_strtoul(argv[1], NULL, 10);
		if (start_bus < 0 || start_bus >= g_master->num_bus) {
			printf("Usage: w1 [0-%d]\n", (g_master->num_bus - 1));
			return rcode;
		}
	}

	/* Loop through bus if no bus specified */
	for (bus = start_bus; bus <= end_bus; bus++) {

		/* Set the global bus */
		g_bus = bus;

		/* Read the eeprom */
		if (w1_eeprom_read(data, 0, 16) < 0) {
			printf("%d: no device detected\n", bus);
			continue;
		}

		/* Display the data */
		printf("%d: ", bus);
		for (i = 0; i < 16; i++) {
			printf("%02x", data[i]);
		}
		printf("\n");
	}

	return rcode;
}

U_BOOT_CMD(
	w1, 2, 2, do_w1,
	"dump eeprom on the 1 wire bus",
	"[bus] - bus to probe\n"
);
