/*-
 * Copyright (c) 2008 John Hay.  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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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 <sys/cdefs.h>
__FBSDID("$FreeBSD: release/9.0.0/sys/boot/arm/ixp425/boot2/ixp425_board.c 215034 2010-11-09 10:59:09Z brucec $");
#include <sys/param.h>
#include <sys/ata.h>
#include <sys/linker_set.h>

#include <stdarg.h>

#include "lib.h"
#include "cf_ata.h"

#include <machine/armreg.h>
#include <arm/xscale/ixp425/ixp425reg.h>
#include <dev/ic/ns16550.h>

struct board_config {
	const char *desc;
	int	(*probe)(int boardtype_hint);
	void	(*init)(void);
};
/* set of registered boards */
SET_DECLARE(boards, struct board_config);
#define	BOARD_CONFIG(name, _desc)			\
static struct board_config name##config = {		\
	.desc	= _desc,				\
	.probe	= name##_probe,				\
	.init	= name##_init,				\
};							\
DATA_SET(boards, name##config)

static u_int cputype;
#define	cpu_is_ixp43x()	(cputype == CPU_ID_IXP435)
static u_int8_t *ubase;

static u_int8_t uart_getreg(u_int8_t *, int);
static void uart_setreg(u_int8_t *, int, u_int8_t);

static void cf_init(void);
static void cf_clr(void);

#ifdef DEBUG
#define	DPRINTF(fmt, ...) printf(fmt, __VA_ARGS__)
#else
#define	DPRINTF(fmt, ...)
#endif

const char *
board_init(void)
{
	struct board_config **pbp;

	cputype = cpu_id() & CPU_ID_CPU_MASK;

	SET_FOREACH(pbp, boards)
		/* XXX pass down redboot board type */
		if ((*pbp)->probe(0)) {
			(*pbp)->init();
			return (*pbp)->desc;
		}
	/* XXX panic, unknown board type */
	return "???";
}

/*
 * This should be called just before starting the kernel. This is so
 * that one can undo incompatible hardware settings.
 */
void
clr_board(void)
{
	cf_clr();
}

/*
 * General support functions.
 */

/*
 * DELAY should delay for the number of microseconds.
 * The idea is that the inner loop should take 1us, so val is the
 * number of usecs to delay.
 */
void
DELAY(int val)
{
	volatile int sub;
	volatile int subsub;

	sub = val;
	while(sub) {
		subsub = 3;
		while(subsub)
			subsub--;
		sub--;
	}
}

u_int32_t
swap32(u_int32_t a)
{
	return (((a & 0xff) << 24) | ((a & 0xff00) << 8) |
	    ((a & 0xff0000) >> 8) | ((a & 0xff000000) >> 24));
}

u_int16_t
swap16(u_int16_t val)
{
	return (val << 8) | (val >> 8);
}

/*
 * uart related funcs
 */
static u_int8_t
uart_getreg(u_int8_t *bas, int off)
{
	return *((volatile u_int32_t *)(bas + (off << 2))) & 0xff;
}

static void
uart_setreg(u_int8_t *bas, int off, u_int8_t val)
{
	*((volatile u_int32_t *)(bas + (off << 2))) = (u_int32_t)val;
}

int
getc(int seconds)
{
	int c, delay, limit;

	c = 0;
	delay = 10000;
	limit = seconds * 1000000/10000;
	while ((uart_getreg(ubase, REG_LSR) & LSR_RXRDY) == 0 && --limit)
		DELAY(delay);

	if ((uart_getreg(ubase, REG_LSR) & LSR_RXRDY) == LSR_RXRDY)
		c = uart_getreg(ubase, REG_DATA);

	return c;
}

void
putchar(int ch)
{
	int delay, limit;

	delay = 500;
	limit = 20;
	while ((uart_getreg(ubase, REG_LSR) & LSR_THRE) == 0 && --limit)
		DELAY(delay);
	uart_setreg(ubase, REG_DATA, ch);

	limit = 40;
	while ((uart_getreg(ubase, REG_LSR) & LSR_TEMT) == 0 && --limit)
		DELAY(delay);
}

void
xputchar(int ch)
{
	if (ch == '\n')
		putchar('\r');
	putchar(ch);
}

void
putstr(const char *str)
{
	while(*str)
		xputchar(*str++);
}

void
puthex8(u_int8_t ch)
{
	const char *hex = "0123456789abcdef";

	putchar(hex[ch >> 4]);
	putchar(hex[ch & 0xf]);
}

void
puthexlist(const u_int8_t *str, int length)
{
	while(length) {
		puthex8(*str);
		putchar(' ');
		str++;
		length--;
	}
}

/*
 *
 * CF/IDE functions.
 *
 */

struct {
	u_int64_t dsize;
	u_int64_t total_secs;
	u_int8_t heads;
	u_int8_t sectors;
	u_int32_t cylinders;

	u_int32_t *cs1to;
	u_int32_t *cs2to;

	u_int8_t *cs1;
	u_int8_t *cs2;

	u_int32_t use_lba;
	u_int32_t use_stream8;
	u_int32_t debug;

	u_int8_t status;
	u_int8_t error;
} dskinf;

static void cfenable16(void);
static void cfdisable16(void);
static u_int8_t cfread8(u_int32_t off);
static u_int16_t cfread16(u_int32_t off);
static void cfreadstream8(void *buf, int length);
static void cfreadstream16(void *buf, int length);
static void cfwrite8(u_int32_t off, u_int8_t val);
static u_int8_t cfaltread8(u_int32_t off);
static void cfaltwrite8(u_int32_t off, u_int8_t val);
static int cfwait(u_int8_t mask);
static int cfaltwait(u_int8_t mask);
static int cfcmd(u_int32_t cmd, u_int32_t cylinder, u_int32_t head,
    u_int32_t sector, u_int32_t count, u_int32_t feature);
static void cfreset(void);
#ifdef DEBUG
static int cfgetparams(void);
#endif
static void cfprintregs(void);

static void
cf_init(void)
{
	u_int8_t status;
#ifdef DEBUG
	int rval;
#endif

	/* NB: board init routines setup other parts of dskinf */
	dskinf.use_stream8 = 0;
	dskinf.use_lba = 0;
	dskinf.debug = 1;

	DPRINTF("cs1 %x, cs2 %x\n", dskinf.cs1, dskinf.cs2);

	/* Setup the CF window */
	*dskinf.cs1to |= (EXP_BYTE_EN | EXP_WR_EN | EXP_BYTE_RD16 | EXP_CS_EN);
	DPRINTF("t1 %x, ", *dskinf.cs1to);

	*dskinf.cs2to |= (EXP_BYTE_EN | EXP_WR_EN | EXP_BYTE_RD16 | EXP_CS_EN);
	DPRINTF("t2 %x\n", *dskinf.cs2to);

	/* Detect if there is a disk. */
	cfwrite8(CF_DRV_HEAD, CF_D_IBM);
	DELAY(1000);
	status = cfread8(CF_STATUS);
	if (status != 0x50)
		printf("cf-ata0 %x\n", (u_int32_t)status);
	if (status == 0xff) {
		printf("cf_ata0: No disk!\n");
		return;
	}

	cfreset();

	if (dskinf.use_stream8) {
		DPRINTF("setting %d bit mode.\n", 8);
		cfwrite8(CF_FEATURE, 0x01); /* Enable 8 bit transfers */
		cfwrite8(CF_COMMAND, ATA_SETFEATURES);
		cfaltwait(CF_S_READY);
	}

#ifdef DEBUG
	rval = cfgetparams();
	if (rval)
		return;
#endif
	dskinf.use_lba = 1;
	dskinf.debug = 0;
}

static void
cf_clr(void)
{
	cfwrite8(CF_DRV_HEAD, CF_D_IBM);
	cfaltwait(CF_S_READY);
	cfwrite8(CF_FEATURE, 0x81); /* Enable 8 bit transfers */
	cfwrite8(CF_COMMAND, ATA_SETFEATURES);
	cfaltwait(CF_S_READY);
}

static void
cfenable16(void)
{
	u_int32_t val;

	val = *dskinf.cs1to;
	*dskinf.cs1to = val &~ EXP_BYTE_EN;
	DELAY(100);
#if 0
	DPRINTF("%s: cs1 timing reg %x\n", *dskinf.cs1to, __func__);
#endif
}

static void
cfdisable16(void)
{
	u_int32_t val;

	DELAY(100);
	val = *dskinf.cs1to;
	*dskinf.cs1to = val | EXP_BYTE_EN;
#if 0
	DPRINTF("%s: cs1 timing reg %x\n", *dskinf.cs1to, __func__);
#endif
}

static u_int8_t
cfread8(u_int32_t off)
{
	volatile u_int8_t *vp;

	vp = (volatile u_int8_t *)(dskinf.cs1 + off);
	return *vp;
}

static void
cfreadstream8(void *buf, int length)
{
	u_int8_t *lbuf;
	u_int8_t tmp;

	lbuf = buf;
	while (length) {
		tmp = cfread8(CF_DATA);
		*lbuf = tmp;
#ifdef DEBUG
		if (dskinf.debug && (length > (512 - 32))) {
			if ((length % 16) == 0)
				xputchar('\n');
			puthex8(tmp);
			putchar(' ');
		}
#endif
		lbuf++;
		length--;
	}
#ifdef DEBUG
	if (dskinf.debug)
		xputchar('\n');
#endif
}

static u_int16_t
cfread16(u_int32_t off)
{
	volatile u_int16_t *vp;

	vp = (volatile u_int16_t *)(dskinf.cs1 + off);
	return swap16(*vp);
}

static void
cfreadstream16(void *buf, int length)
{
	u_int16_t *lbuf;

	length = length / 2;
	cfenable16();
	lbuf = buf;
	while (length--) {
		*lbuf = cfread16(CF_DATA);
		lbuf++;
	}
	cfdisable16();
}

static void
cfwrite8(u_int32_t off, u_int8_t val)
{
	volatile u_int8_t *vp;

	vp = (volatile u_int8_t *)(dskinf.cs1 + off);
	*vp = val;
}

#if 0
static void
cfwrite16(u_int32_t off, u_int16_t val)
{
	volatile u_int16_t *vp;

	vp = (volatile u_int16_t *)(dskinf.cs1 + off);
	*vp = val;
}
#endif

static u_int8_t
cfaltread8(u_int32_t off)
{
	volatile u_int8_t *vp;

	off &= 0x0f;
	vp = (volatile u_int8_t *)(dskinf.cs2 + off);
	return *vp;
}

static void
cfaltwrite8(u_int32_t off, u_int8_t val)
{
	volatile u_int8_t *vp;

	/*
	 * This is documented in the Intel appnote 302456.
	 */
	off &= 0x0f;
	vp = (volatile u_int8_t *)(dskinf.cs2 + off);
	*vp = val;
}

static int
cfwait(u_int8_t mask)
{
	u_int8_t status;
	u_int32_t tout;

	tout = 0;
	while (tout <= 5000000) {
		status = cfread8(CF_STATUS);
		if (status == 0xff) {
			printf("%s: master: no status, reselecting\n",
			    __func__);
			cfwrite8(CF_DRV_HEAD, CF_D_IBM);
			DELAY(1);
			status = cfread8(CF_STATUS);
		}
		if (status == 0xff)
			return -1;
		dskinf.status = status;
		if (!(status & CF_S_BUSY)) {
			if (status & CF_S_ERROR) {
				dskinf.error = cfread8(CF_ERROR);
				printf("%s: error, status 0x%x error 0x%x\n",
				    __func__, status, dskinf.error);
			}
			if ((status & mask) == mask) {
				DPRINTF("%s: status 0x%x mask 0x%x tout %u\n",
				    __func__, status, mask, tout);
				return (status & CF_S_ERROR);
			}
		}
		if (tout > 1000) {
			tout += 1000;
			DELAY(1000);
		} else {
			tout += 10;
			DELAY(10);
		}
	}
	return -1;
}

static int
cfaltwait(u_int8_t mask)
{
	u_int8_t status;
	u_int32_t tout;

	tout = 0;
	while (tout <= 5000000) {
		status = cfaltread8(CF_ALT_STATUS);
		if (status == 0xff) {
			printf("cfaltwait: master: no status, reselecting\n");
			cfwrite8(CF_DRV_HEAD, CF_D_IBM);
			DELAY(1);
			status = cfread8(CF_STATUS);
		}
		if (status == 0xff)
			return -1;
		dskinf.status = status;
		if (!(status & CF_S_BUSY)) {
			if (status & CF_S_ERROR)
				dskinf.error = cfread8(CF_ERROR);
			if ((status & mask) == mask) {
				DPRINTF("cfaltwait: tout %u\n", tout);
				return (status & CF_S_ERROR);
			}
		}
		if (tout > 1000) {
			tout += 1000;
			DELAY(1000);
		} else {
			tout += 10;
			DELAY(10);
		}
	}
	return -1;
}

static int
cfcmd(u_int32_t cmd, u_int32_t cylinder, u_int32_t head, u_int32_t sector,
    u_int32_t count, u_int32_t feature)
{
	if (cfwait(0) < 0) {
		printf("cfcmd: timeout\n");
		return -1;
	}
	cfwrite8(CF_FEATURE, feature);
	cfwrite8(CF_CYL_L, cylinder);
	cfwrite8(CF_CYL_H, cylinder >> 8);
	if (dskinf.use_lba)
		cfwrite8(CF_DRV_HEAD, CF_D_IBM | CF_D_LBA | head);
	else
		cfwrite8(CF_DRV_HEAD, CF_D_IBM | head);
	cfwrite8(CF_SECT_NUM, sector);
	cfwrite8(CF_SECT_CNT, count);
	cfwrite8(CF_COMMAND, cmd);
	return 0;
}

static void
cfreset(void)
{
	u_int8_t status;
	u_int32_t tout;

	cfwrite8(CF_DRV_HEAD, CF_D_IBM);
	DELAY(1);
#ifdef DEBUG
	cfprintregs();
#endif
	cfread8(CF_STATUS);
	cfaltwrite8(CF_ALT_DEV_CTR, CF_A_IDS | CF_A_RESET);
	DELAY(10000);
	cfaltwrite8(CF_ALT_DEV_CTR, CF_A_IDS);
	DELAY(10000);
	cfread8(CF_ERROR);
	DELAY(3000);

	for (tout = 0; tout < 310000; tout++) {
		cfwrite8(CF_DRV_HEAD, CF_D_IBM);
		DELAY(1);
		status = cfread8(CF_STATUS);
		if (!(status & CF_S_BUSY))
			break;
		DELAY(100);
	}
	DELAY(1);
	if (status & CF_S_BUSY) {
		cfprintregs();
		printf("cfreset: Status stayed busy after reset.\n");
	}
	DPRINTF("cfreset: finished, tout %u\n", tout);
}

#ifdef DEBUG
static int
cfgetparams(void)
{
	u_int8_t *buf;

	buf = (u_int8_t *)(0x170000);
	p_memset((char *)buf, 0, 1024);
	/* Select the drive. */
	cfwrite8(CF_DRV_HEAD, CF_D_IBM);
	DELAY(1);
	cfcmd(ATA_ATA_IDENTIFY, 0, 0, 0, 0, 0);
	if (cfaltwait(CF_S_READY | CF_S_DSC | CF_S_DRQ)) {
		printf("cfgetparams: ATA_IDENTIFY failed.\n");
		return -1;
	}
	if (dskinf.use_stream8)
		cfreadstream8(buf, 512);
	else
		cfreadstream16(buf, 512);
	if (dskinf.debug)
		cfprintregs();
#if 0
	memcpy(&dskinf.ata_params, buf, sizeof(struct ata_params));
	dskinf.cylinders = dskinf.ata_params.cylinders;
	dskinf.heads = dskinf.ata_params.heads;
	dskinf.sectors = dskinf.ata_params.sectors;
	printf("dsk0: sec %x, hd %x, cyl %x, stat %x, err %x\n",
	    (u_int32_t)dskinf.ata_params.sectors,
	    (u_int32_t)dskinf.ata_params.heads,
	    (u_int32_t)dskinf.ata_params.cylinders,
	    (u_int32_t)dskinf.status,
	    (u_int32_t)dskinf.error);
#endif
	dskinf.status = cfread8(CF_STATUS);
	if (dskinf.debug)
		printf("cfgetparams: ata_params * %x, stat %x\n",
		    (u_int32_t)buf, (u_int32_t)dskinf.status);
	return 0;
}
#endif /* DEBUG */

static void
cfprintregs(void)
{
	u_int8_t rv;

	putstr("cfprintregs: regs error ");
	rv = cfread8(CF_ERROR);
	puthex8(rv);
	putstr(", count ");
	rv = cfread8(CF_SECT_CNT);
	puthex8(rv);
	putstr(", sect ");
	rv = cfread8(CF_SECT_NUM);
	puthex8(rv);
	putstr(", cyl low ");
	rv = cfread8(CF_CYL_L);
	puthex8(rv);
	putstr(", cyl high ");
	rv = cfread8(CF_CYL_H);
	puthex8(rv);
	putstr(", drv head ");
	rv = cfread8(CF_DRV_HEAD);
	puthex8(rv);
	putstr(", status ");
	rv = cfread8(CF_STATUS);
	puthex8(rv);
	putstr("\n");
}

int
avila_read(char *dest, unsigned source, unsigned length)
{
	if (dskinf.use_lba == 0 && source == 0)
		source++;
	if (dskinf.debug)
		printf("avila_read: 0x%x, sect %d num secs %d\n",
		    (u_int32_t)dest, source, length);
	while (length) {
		cfwait(CF_S_READY);
		/* cmd, cyl, head, sect, count, feature */
		cfcmd(ATA_READ, (source >> 8) & 0xffff, source >> 24,
		    source & 0xff, 1, 0);

		cfwait(CF_S_READY | CF_S_DRQ | CF_S_DSC);
		if (dskinf.use_stream8)
			cfreadstream8(dest, 512);
		else
			cfreadstream16(dest, 512);
		length--;
		source++;
		dest += 512;
	}
	return 0;
}

/*
 * Gateworks Avila Support.
 */
static int
avila_probe(int boardtype_hint)
{
	volatile u_int32_t *cs;
	/*
	 * Redboot only configure the chip selects that are needed, so
	 * use that to figure out if it is an Avila or ADI board. The
	 * Avila boards use CS2 and ADI does not.
	 */
	cs = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET);
	return (*cs != 0);
}

static void
avila_init(void)
{
	/* Config the serial port. RedBoot should do the rest. */
	ubase = (u_int8_t *)(IXP425_UART0_HWBASE);

	dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS1_OFFSET);
	dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET);
	dskinf.cs1 = (u_int8_t *)IXP425_EXP_BUS_CS1_HWBASE;
	dskinf.cs2 = (u_int8_t *)IXP425_EXP_BUS_CS2_HWBASE;

	cf_init();
}
BOARD_CONFIG(avila, "Gateworks Avila");

/*
 * Gateworks Cambria Support.
 */
static int
cambria_probe(int boardtype_hint)
{
	return cpu_is_ixp43x();
}

static void
cambria_init(void)
{
	/* Config the serial port. RedBoot should do the rest. */
	ubase = (u_int8_t *)(IXP425_UART0_HWBASE);

	dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS3_OFFSET);
	dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS4_OFFSET);
	dskinf.cs1 = (u_int8_t *)CAMBRIA_CFSEL0_HWBASE;
	dskinf.cs2 = (u_int8_t *)CAMBRIA_CFSEL1_HWBASE;

	cf_init();
}
BOARD_CONFIG(cambria, "Gateworks Cambria");

/*
 * Pronghorn Metro Support.
 */
static int
pronghorn_probe(int boardtype_hint)
{
	volatile u_int32_t *cs;
	/*
	 * Redboot only configure the chip selects that are needed, so
	 * use that to figure out if it is an Avila or ADI board. The
	 * Avila boards use CS2 and ADI does not.
	 */
	cs = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET);
	return (*cs == 0);
}

static void
pronghorn_init(void)
{
	/* Config the serial port. RedBoot should do the rest. */
	ubase = (u_int8_t *)(IXP425_UART1_HWBASE);

	dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS3_OFFSET);
	dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS4_OFFSET);
	dskinf.cs1 = (u_int8_t *)IXP425_EXP_BUS_CS3_HWBASE;
	dskinf.cs2 = (u_int8_t *)IXP425_EXP_BUS_CS4_HWBASE;

	cf_init();
}
BOARD_CONFIG(pronghorn, "Pronghorn Metro");
