/* $NetBSD: brdsetup.c,v 1.42 2023/12/20 15:29:07 thorpej Exp $ */

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Tohru Nishimura.
 *
 * 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 NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS
 * 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/param.h>

#include <powerpc/psl.h>
#include <powerpc/oea/spr.h>

#include <lib/libsa/stand.h>
#include <lib/libsa/net.h>
#include <lib/libkern/libkern.h>

#include <machine/bootinfo.h>

#include "globals.h"

#define BRD_DECL(xxx) \
    void xxx ## setup(struct brdprop *); \
    void xxx ## brdfix(struct brdprop *); \
    void xxx ## pcifix(struct brdprop *); \
    void xxx ## launch(struct brdprop *); \
    void xxx ## reset(void)

BRD_DECL(mot);
BRD_DECL(enc);
BRD_DECL(kuro);
BRD_DECL(syno);
BRD_DECL(qnap);
BRD_DECL(iomega);
BRD_DECL(dlink);
BRD_DECL(nhnas);
BRD_DECL(kurot4);

static void brdfixup(void);
static void setup(void);
static void send_iomega(int, int, int, int, int, int);
static inline uint32_t mfmsr(void);
static inline void mtmsr(uint32_t);
static inline uint32_t cputype(void);
static inline uint64_t mftb(void);
static void init_uart(unsigned, unsigned, uint8_t);
static void send_sat(char *);
static unsigned mpc107memsize(void);

/* UART registers */
#define RBR		0
#define THR		0
#define DLB		0
#define DMB		1
#define IER		1
#define FCR		2
#define LCR		3
#define  LCR_DLAB	0x80
#define  LCR_PEVEN	0x18
#define  LCR_PNONE	0x00
#define  LCR_8BITS	0x03
#define MCR		4
#define  MCR_RTS	0x02
#define  MCR_DTR	0x01
#define LSR		5
#define  LSR_THRE	0x20
#define  LSR_DRDY	0x01
#define DCR		0x11
#define UART_READ(base, r)	in8(base + (r))
#define UART_WRITE(base, r, v)	out8(base + (r), (v))

/* MPC106 and MPC824x PCI bridge memory configuration */
#define MPC106_MEMSTARTADDR1	0x80
#define MPC106_EXTMEMSTARTADDR1	0x88
#define MPC106_MEMENDADDR1	0x90
#define MPC106_EXTMEMENDADDR1	0x98
#define MPC106_MEMEN		0xa0

/* Iomega StorCenter MC68HC908 microcontroller data packet */
#define IOMEGA_POWER		0
#define IOMEGA_LED		1
#define IOMEGA_FLASH_RATE	2
#define IOMEGA_FAN		3
#define IOMEGA_HIGH_TEMP	4
#define IOMEGA_LOW_TEMP		5
#define IOMEGA_ID		6
#define IOMEGA_CHECKSUM		7
#define IOMEGA_PACKETSIZE	8

/* NH230/231 GPIO */
#define NHGPIO_WRITE(x)		*((volatile uint8_t *)0x70000000) = (x)

/* Synology CPLD (2007 and newer models) */
#define SYNOCPLD_READ(r)	*((volatile uint8_t *)0xff000000 + (r))
#define SYNOCPLD_WRITE(r,x)	do { \
    *((volatile uint8_t *)0xff000000 + (r)) = (x); \
    delay(10); \
    } while(0)

static struct brdprop brdlist[] = {
    {
	"sandpoint",
	"Sandpoint X3",
	BRD_SANDPOINTX3,
	0,
	"com", 0x3f8, 115200,
	motsetup, motbrdfix, motpcifix, NULL, NULL },
    {
	"encpp1",
	"EnCore PP1",
	BRD_ENCOREPP1,
	0,
	"com", 0x3f8, 115200,
	encsetup, encbrdfix, encpcifix, NULL, NULL },
    {
	"kurobox",
	"KuroBox",
	BRD_KUROBOX,
	0,
	"eumb", 0x4600, 57600,
	kurosetup, kurobrdfix, NULL, NULL, kuroreset },
    {
	"synology",
	"Synology CS/DS/RS",
	BRD_SYNOLOGY,
	0,
	"eumb", 0x4500, 115200,
	synosetup, synobrdfix, synopcifix, synolaunch, synoreset },
    {
	"qnap",
	"QNAP TS",
	BRD_QNAPTS,
	33164691,	/* Linux source says 33000000, but the Synology  */
			/* clock value delivers a much better precision. */
	"eumb", 0x4500, 115200,
	NULL, qnapbrdfix, NULL, NULL, qnapreset },
    {
	"iomega",
	"IOMEGA StorCenter G2",
	BRD_STORCENTER,
	0,
	"eumb", 0x4500, 115200,
	NULL, iomegabrdfix, NULL, NULL, iomegareset },
    {
	"dlink",
	"D-Link DSM-G600",
	BRD_DLINKDSM,
	33000000,
	"eumb", 0x4500, 9600,
	NULL, dlinkbrdfix, NULL, NULL, NULL },
    {
	"nhnas",
	"Netronix NH-230/231",
	BRD_NH230NAS,
	33000000,
	"eumb", 0x4500, 9600,
	NULL, nhnasbrdfix, NULL, NULL, nhnasreset },
    {
	"kurot4",
	"KuroBox/T4",
	BRD_KUROBOXT4,
	32768000,
	"eumb", 0x4600, 57600,
	NULL, kurot4brdfix, NULL, NULL, NULL },
    {
	"unknown",
	"Unknown board",
	BRD_UNKNOWN,
	0,
	"eumb", 0x4500, 115200,
	NULL, NULL, NULL, NULL, NULL }, /* must be the last */
};

static struct brdprop *brdprop;
static uint32_t ticks_per_sec, ns_per_tick;

const unsigned dcache_line_size = 32;		/* 32B linesize */
const unsigned dcache_range_size = 4 * 1024;	/* 16KB / 4-way */

unsigned uart1base;	/* console */
unsigned uart2base;	/* optional satellite processor */

void brdsetup(void);	/* called by entry.S */

void
brdsetup(void)
{
	static uint8_t pci_to_memclk[] = {
		30, 30, 10, 10, 20, 10, 10, 10,
		10, 20, 20, 15, 20, 15, 20, 30,
		30, 40, 15, 40, 20, 25, 20, 40,
		25, 20, 10, 20, 15, 15, 20, 00
	};
	static uint8_t mem_to_cpuclk[] = {
		25, 30, 45, 20, 20, 00, 10, 30,
		30, 20, 45, 30, 25, 35, 30, 35,
		20, 25, 20, 30, 35, 40, 40, 20,
		30, 25, 40, 30, 30, 25, 35, 00
	};
	char *consname;
	int consport;
	uint32_t extclk;
	unsigned pchb, pcib, dev11, dev12, dev13, dev15, dev16, val;
	extern struct btinfo_memory bi_mem;
	extern struct btinfo_console bi_cons;
	extern struct btinfo_clock bi_clk;
	extern struct btinfo_prodfamily bi_fam;

	/*
	 * CHRP specification "Map-B" BAT012 layout
	 *   BAT0 0000-0000 (256MB) SDRAM
	 *   BAT1 8000-0000 (256MB) PCI mem space
	 *   BAT2 fc00-0000 (64MB)  EUMB, PCI I/O space, misc devs, flash
	 *
	 * EUMBBAR is at fc00-0000.
	 */
	pchb = pcimaketag(0, 0, 0);
	pcicfgwrite(pchb, 0x78, 0xfc000000);

	brdtype = BRD_UNKNOWN;
	extclk = EXT_CLK_FREQ;	/* usually 33MHz */
	busclock = 0;

	dev11 = pcimaketag(0, 11, 0);
	dev12 = pcimaketag(0, 12, 0);
	dev13 = pcimaketag(0, 13, 0);
	dev15 = pcimaketag(0, 15, 0);
	dev16 = pcimaketag(0, 16, 0);

	if (pcifinddev(0x10ad, 0x0565, &pcib) == 0) {
		/* WinBond 553 southbridge at dev 11 */
		brdtype = BRD_SANDPOINTX3;
	}
	else if (pcifinddev(0x1106, 0x0686, &pcib) == 0) {
		/* VIA 686B southbridge at dev 22 */
		brdtype = BRD_ENCOREPP1;
	}
	else if (PCI_CLASS(pcicfgread(dev11, PCI_CLASS_REG)) == PCI_CLASS_ETH) {
		/* ADMtek AN985 (tlp) or RealTek 8169S (re) at dev 11 */
		if (PCI_VENDOR(pcicfgread(dev11, PCI_ID_REG)) == 0x1317)
			brdtype = BRD_KUROBOX;
		else if (PCI_VENDOR(pcicfgread(dev11, PCI_ID_REG)) == 0x10ec) {
			if (PCI_PRODUCT(pcicfgread(dev12,PCI_ID_REG)) != 0x3512)
				brdtype = BRD_KUROBOX;
			else
				brdtype = BRD_KUROBOXT4;
		}
	}
	else if (PCI_VENDOR(pcicfgread(dev15, PCI_ID_REG)) == 0x1148
	    || PCI_VENDOR(pcicfgread(dev15, PCI_ID_REG)) == 0x11ab) {
		/* SKnet/Marvell (sk) at dev 15 */
		brdtype = BRD_SYNOLOGY;
	}
	else if (PCI_VENDOR(pcicfgread(dev13, PCI_ID_REG)) == 0x1106) {
		/* VIA 6410 (viaide) at dev 13 */
		brdtype = BRD_STORCENTER;
	}
	else if (PCI_VENDOR(pcicfgread(dev16, PCI_ID_REG)) == 0x1191) {
		/* ACARD ATP865 (acardide) at dev 16 */
		brdtype = BRD_DLINKDSM;
	}
	else if (PCI_VENDOR(pcicfgread(dev16, PCI_ID_REG)) == 0x1283
	    || PCI_VENDOR(pcicfgread(dev16, PCI_ID_REG)) == 0x1095) {
		/* ITE (iteide) or SiI (satalink) at dev 16 */
		brdtype = BRD_NH230NAS;
	}
	else if (PCI_VENDOR(pcicfgread(dev15, PCI_ID_REG)) == 0x8086
	    || PCI_VENDOR(pcicfgread(dev15, PCI_ID_REG)) == 0x10ec) {
		/* Intel (wm) or RealTek (re) at dev 15 */
		brdtype = BRD_QNAPTS;
	}

	brdprop = brd_lookup(brdtype);

	/* brd dependent adjustments */
	setup();

	/* determine clock frequencies */
	if (brdprop->extclk != 0)
		extclk = brdprop->extclk;
	if (busclock == 0) {
		if (cputype() == MPC8245) {
			/* PLL_CFG from PCI host bridge register 0xe2 */
			val = pcicfgread(pchb, 0xe0);
			busclock = (extclk *
			    pci_to_memclk[(val >> 19) & 0x1f] + 10) / 10;
			/* PLLRATIO from HID1 */
			asm volatile ("mfspr %0,1009" : "=r"(val));
			cpuclock = ((uint64_t)busclock *
			    mem_to_cpuclk[val >> 27] + 10) / 10;
		} else
			busclock = 100000000;	/* 100MHz bus clock default */
	}
	ticks_per_sec = busclock >> 2;
	ns_per_tick = 1000000000 / ticks_per_sec;

	/* now prepare serial console */
	consname = brdprop->consname;
	consport = brdprop->consport;
	if (strcmp(consname, "eumb") == 0) {
		uart1base = 0xfc000000 + consport;	/* 0x4500, 0x4600 */
		UART_WRITE(uart1base, DCR, 0x01);	/* enable DUART mode */
		uart2base = uart1base ^ 0x0300;
	} else
		uart1base = 0xfe000000 + consport;	/* 0x3f8, 0x2f8 */

	/* more brd adjustments */
	brdfixup();

	bi_mem.memsize = mpc107memsize();
	snprintf(bi_cons.devname, sizeof(bi_cons.devname), "%s", consname);
	bi_cons.addr = consport;
	bi_cons.speed = brdprop->consspeed;
	bi_clk.ticks_per_sec = ticks_per_sec;
	snprintf(bi_fam.name, sizeof(bi_fam.name), "%s", brdprop->family);
}

struct brdprop *
brd_lookup(int brd)
{
	u_int i;

	for (i = 0; i < sizeof(brdlist)/sizeof(brdlist[0]); i++) {
		if (brdlist[i].brdtype == brd)
			return &brdlist[i];
	}
	return &brdlist[i - 1];
}

static void
setup()
{

	if (brdprop->setup == NULL)
		return;
	(*brdprop->setup)(brdprop);
}

static void
brdfixup()
{

	if (brdprop->brdfix == NULL)
		return;
	(*brdprop->brdfix)(brdprop);
}

void
pcifixup()
{

	if (brdprop->pcifix == NULL)
		return;
	(*brdprop->pcifix)(brdprop);
}

void
launchfixup()
{

	if (brdprop->launch == NULL)
		return;
	(*brdprop->launch)(brdprop);
}

void
encsetup(struct brdprop *brd)
{

#ifdef COSNAME
	brd->consname = CONSNAME;
#endif
#ifdef CONSPORT
	brd->consport = CONSPORT;
#endif
#ifdef CONSSPEED
	brd->consspeed = CONSSPEED;
#endif
}

void
encbrdfix(struct brdprop *brd)
{
	unsigned ac97, ide, pcib, pmgt, usb12, usb34, val;

/*
 * VIA82C686B Southbridge
 *	0.22.0	1106.0686	PCI-ISA bridge
 *	0.22.1	1106.0571	IDE (viaide)
 *	0.22.2	1106.3038	USB 0/1 (uhci)
 *	0.22.3	1106.3038	USB 2/3 (uhci)
 *	0.22.4	1106.3057	power management
 *	0.22.5	1106.3058	AC97 (auvia)
 */
	pcib  = pcimaketag(0, 22, 0);
	ide   = pcimaketag(0, 22, 1);
	usb12 = pcimaketag(0, 22, 2);
	usb34 = pcimaketag(0, 22, 3);
	pmgt  = pcimaketag(0, 22, 4);
	ac97  = pcimaketag(0, 22, 5);

#define	CFG(i,v) do { \
   *(volatile unsigned char *)(0xfe000000 + 0x3f0) = (i); \
   *(volatile unsigned char *)(0xfe000000 + 0x3f1) = (v); \
   } while (0)
	val = pcicfgread(pcib, 0x84);
	val |= (02 << 8);
	pcicfgwrite(pcib, 0x84, val);
	CFG(0xe2, 0x0f); /* use COM1/2, don't use FDC/LPT */
	val = pcicfgread(pcib, 0x84);
	val &= ~(02 << 8);
	pcicfgwrite(pcib, 0x84, val);

	/* route pin C to i8259 IRQ 5, pin D to 11 */
	val = pcicfgread(pcib, 0x54);
	val = (val & 0xff) | 0xb0500000; /* Dx CB Ax xS */
	pcicfgwrite(pcib, 0x54, val);

	/* enable EISA ELCR1 (0x4d0) and ELCR2 (0x4d1) */
	val = pcicfgread(pcib, 0x44);
	val = val | 0x20000000;
	pcicfgwrite(pcib, 0x44, val);

	/* select level trigger for IRQ 5/11 at ELCR1/2 */
	*(volatile uint8_t *)0xfe0004d0 = 0x20; /* bit 5 */
	*(volatile uint8_t *)0xfe0004d1 = 0x08; /* bit 11 */

	/* USB and AC97 are hardwired with pin D and C */
	val = pcicfgread(usb12, 0x3c) &~ 0xff;
	val |= 11;
	pcicfgwrite(usb12, 0x3c, val);
	val = pcicfgread(usb34, 0x3c) &~ 0xff;
	val |= 11;
	pcicfgwrite(usb34, 0x3c, val);
	val = pcicfgread(ac97, 0x3c) &~ 0xff;
	val |= 5;
	pcicfgwrite(ac97, 0x3c, val);

	(void) pcicfgread(ide, 0x08);
	(void) pcicfgread(pmgt, 0x08);
}

void
encpcifix(struct brdprop *brd)
{
	unsigned ide, irq, net, pcib, steer, val;

#define	STEER(v, b) (((v) & (b)) ? "edge" : "level")
	pcib = pcimaketag(0, 22, 0);
	ide  = pcimaketag(0, 22, 1);
	net  = pcimaketag(0, 25, 0);

	/*
	 * //// VIA PIRQ ////
	 * 0x57/56/55/54 - Dx CB Ax xS
	 */
	val = pcicfgread(pcib, 0x54);	/* Dx CB Ax xs */
	steer = val & 0xf;
	irq = (val >> 12) & 0xf;	/* 15:12 */
	if (irq) {
		printf("pin A -> irq %d, %s\n",
			irq, STEER(steer, 0x1));
	}
	irq = (val >> 16) & 0xf;	/* 19:16 */
	if (irq) {
		printf("pin B -> irq %d, %s\n",
			irq, STEER(steer, 0x2));
	}
	irq = (val >> 20) & 0xf;	/* 23:20 */
	if (irq) {
		printf("pin C -> irq %d, %s\n",
			irq, STEER(steer, 0x4));
	}
	irq = (val >> 28);		/* 31:28 */
	if (irq) {
		printf("pin D -> irq %d, %s\n",
			irq, STEER(steer, 0x8));
	}
#if 0
	/*
	 * //// IDE fixup ////
	 * - "native mode" (ide 0x09)
	 */

	/* ide: 0x09 - programming interface; 1000'SsPp */
	val = pcicfgread(ide, 0x08) & 0xffff00ff;
	pcicfgwrite(ide, 0x08, val | (0x8f << 8));

	/* ide: 0x10-20 - leave them PCI memory space assigned */
#else
	/*
	 * //// IDE fixup ////
	 * - "compatibility mode" (ide 0x09)
	 * - remove PCI pin assignment (ide 0x3d)
	 */

	/* ide: 0x09 - programming interface; 1000'SsPp */
	val = pcicfgread(ide, 0x08) & 0xffff00ff;
	val |= (0x8a << 8);
	pcicfgwrite(ide, 0x08, val);

	/* ide: 0x10-20 */
	/*
	 * experiment shows writing ide: 0x09 changes these
	 * register behaviour. The pcicfgwrite() above writes
	 * 0x8a at ide: 0x09 to make sure legacy IDE.  Then
	 * reading BAR0-3 is to return value 0s even though
	 * pcisetup() has written range assignments.  Value
	 * overwrite makes no effect. Having 0x8f for native
	 * PCIIDE doesn't change register values and brings no
	 * weirdness.
	 */

	/* ide: 0x3d/3c - turn off PCI pin */
	val = pcicfgread(ide, 0x3c) & 0xffff00ff;
	pcicfgwrite(ide, 0x3c, val);
#endif
	/*
	 * //// USBx2, audio, and modem fixup ////
	 * - disable USB #0 and #1 (pcib 0x48 and 0x85)
	 * - disable AC97 audio and MC97 modem (pcib 0x85)
	 */

	/* pcib: 0x48 - disable USB #0 at function 2 */
	val = pcicfgread(pcib, 0x48);
	pcicfgwrite(pcib, 0x48, val | 04);

	/* pcib: 0x85 - disable USB #1 at function 3 */
	/* pcib: 0x85 - disable AC97/MC97 at function 5/6 */
	val = pcicfgread(pcib, 0x84);
	pcicfgwrite(pcib, 0x84, val | 0x1c00);

	/*
	 * //// fxp fixup ////
	 * - use PCI pin A line 25 (fxp 0x3d/3c)
	 */
	/* 0x3d/3c - PCI pin/line */
	val = pcicfgread(net, 0x3c) & 0xffff0000;
	val |= (('A' - '@') << 8) | 25;
	pcicfgwrite(net, 0x3c, val);
}

void
motsetup(struct brdprop *brd)
{

#ifdef COSNAME
	brd->consname = CONSNAME;
#endif
#ifdef CONSPORT
	brd->consport = CONSPORT;
#endif
#ifdef CONSSPEED
	brd->consspeed = CONSSPEED;
#endif
}

void
motbrdfix(struct brdprop *brd)
{

/*
 * WinBond/Symphony Lab 83C553 with PC87308 "SuperIO"
 *
 *	0.11.0	10ad.0565	PCI-ISA bridge
 *	0.11.1	10ad.0105	IDE (slide)
 */
}

void
motpcifix(struct brdprop *brd)
{
	unsigned ide, net, pcib, steer, val;
	int line;

	pcib = pcimaketag(0, 11, 0);
	ide  = pcimaketag(0, 11, 1);
	net  = pcimaketag(0, 15, 0);

	/*
	 * //// WinBond PIRQ ////
	 * 0x40 - bit 5 (0x20) indicates PIRQ presense
	 * 0x60 - PIRQ interrupt routing steer
	 */
	if (pcicfgread(pcib, 0x40) & 0x20) {
		steer = pcicfgread(pcib, 0x60);
		if ((steer & 0x80808080) == 0x80808080)
			printf("PIRQ[0-3] disabled\n");
		else {
			unsigned i, v = steer;
			for (i = 0; i < 4; i++, v >>= 8) {
				if ((v & 0x80) != 0 || (v & 0xf) == 0)
					continue;
				printf("PIRQ[%d]=%d\n", i, v & 0xf);
				}
			}
		}
#if 1
	/*
	 * //// IDE fixup -- case A ////
	 * - "native PCI mode" (ide 0x09)
	 * - don't use ISA IRQ14/15 (pcib 0x43)
	 * - native IDE for both channels (ide 0x40)
	 * - LEGIRQ bit 11 steers interrupt to pin C (ide 0x40)
	 * - sign as PCI pin C line 11 (ide 0x3d/3c)
	 */
	/* ide: 0x09 - programming interface; 1000'SsPp */
	val = pcicfgread(ide, 0x08);
	val &= 0xffff00ff;
	pcicfgwrite(ide, 0x08, val | (0x8f << 8));

	/* pcib: 0x43 - IDE interrupt routing */
	val = pcicfgread(pcib, 0x40) & 0x00ffffff;
	pcicfgwrite(pcib, 0x40, val);

	/* pcib: 0x45/44 - PCI interrupt routing */
	val = pcicfgread(pcib, 0x44) & 0xffff0000;
	pcicfgwrite(pcib, 0x44, val);

	/* ide: 0x41/40 - IDE channel */
	val = pcicfgread(ide, 0x40) & 0xffff0000;
	val |= (1 << 11) | 0x33; /* LEGIRQ turns on PCI interrupt */
	pcicfgwrite(ide, 0x40, val);

	/* ide: 0x3d/3c - use PCI pin C/line 11 */
	val = pcicfgread(ide, 0x3c) & 0xffffff00;
	val |= 11; /* pin designation is hardwired to pin A */
	pcicfgwrite(ide, 0x3c, val);
#else
	/*
	 * //// IDE fixup -- case B ////
	 * - "compatibility mode" (ide 0x09)
	 * - IDE primary/secondary interrupt routing (pcib 0x43)
	 * - PCI interrupt routing (pcib 0x45/44)
	 * - no PCI pin/line assignment (ide 0x3d/3c)
	 */
	/* ide: 0x09 - programming interface; 1000'SsPp */
	val = pcicfgread(ide, 0x08);
	val &= 0xffff00ff;
	pcicfgwrite(ide, 0x08, val | (0x8a << 8));

	/* pcib: 0x43 - IDE interrupt routing */
	val = pcicfgread(pcib, 0x40) & 0x00ffffff;
	pcicfgwrite(pcib, 0x40, val | (0xee << 24));

	/* ide: 0x45/44 - PCI interrupt routing */
	val = pcicfgread(ide, 0x44) & 0xffff0000;
	pcicfgwrite(ide, 0x44, val);

	/* ide: 0x3d/3c - turn off PCI pin/line */
	val = pcicfgread(ide, 0x3c) & 0xffff0000;
	pcicfgwrite(ide, 0x3c, val);
#endif

	/*
	 * //// fxp fixup ////
	 * - use PCI pin A line 15 (fxp 0x3d/3c)
	 */
	val = pcicfgread(net, 0x3c) & 0xffff0000;
	pcidecomposetag(net, NULL, &line, NULL);
	val |= (('A' - '@') << 8) | line;
	pcicfgwrite(net, 0x3c, val);
}

void
kurosetup(struct brdprop *brd)
{

	if (PCI_VENDOR(pcicfgread(pcimaketag(0, 11, 0), PCI_ID_REG)) == 0x10ec)
		brd->extclk = 32768000; /* decr 2457600Hz */
	else
		brd->extclk = 32521333; /* decr 2439100Hz */
}

void
kurobrdfix(struct brdprop *brd)
{

	init_uart(uart2base, 9600, LCR_8BITS | LCR_PEVEN);
	/* Stop Watchdog */
	send_sat("AAAAFFFFJJJJ>>>>VVVV>>>>ZZZZVVVVKKKK");
}

void
kuroreset()
{

	send_sat("CCGG");
	/*NOTREACHED*/
}

void
synosetup(struct brdprop *brd)
{
	/*
	 * My DS107e works much better with the
	 * default EXT_CLK_FREQ (33333333).
	 *   --thorpej
	 */
#if 0
	if (1) /* 200 and 266MHz models */
		brd->extclk = 33164691; /* from Synology/Linux source */
	else   /* 400MHz models XXX how to check? */
		brd->extclk = 33165343;
#endif
}

void
synobrdfix(struct brdprop *brd)
{

	init_uart(uart2base, 9600, LCR_8BITS | LCR_PNONE);
	/* beep, power LED on, status LED off */
	send_sat("247");
}

#define SYNO_FAN_TIMEOUT	500	/* 500ms to turn the fan off */
#define SYNO_DISK_DELAY		30	/* 30 seconds to power up 2nd disk */

void
synopcifix(struct brdprop *brd)
{
	static const char models207[4][7] = {
		"???", "DS107e", "DS107", "DS207"
	};
	static const char models209[2][7] = {
		"DS109j", "DS209j"
	};
	static const char models406[3][7] = {
		"CS406e", "CS406", "RS406"
	};
	static const char models407[4][7] = {
		"???", "CS407e", "CS407", "RS407"
	};
	extern struct btinfo_model bi_model;
	const char *model_name;
	unsigned cpld, version, flags;
	uint8_t v, status;
	int i;

	/*
	 * Determine if a CPLD is present and whether is has 4-bit
	 * (models 107, 207, 209)  or 8-bit (models 406, 407) registers.
	 * The register set repeats every 16 bytes.
	 */
	cpld = 0;
	flags = 0;
	version = 0;
	model_name = NULL;

	SYNOCPLD_WRITE(0, 0x00);	/* LEDs blinking yellow (default) */
	v = SYNOCPLD_READ(0);

	if (v != 0x00) {
		v &= 0xf0;
		if (v != 0x00 || (SYNOCPLD_READ(16 + 0) & 0xf0) != v)
			goto cpld_done;

  cpld4bits:
		/* 4-bit registers assumed, make LEDs solid yellow */
		SYNOCPLD_WRITE(0, 0x50);
		v = SYNOCPLD_READ(0) & 0xf0;
		if (v != 0x50 || (SYNOCPLD_READ(32 + 0) & 0xf0) != v)
			goto cpld_done;

		v = SYNOCPLD_READ(2) & 0xf0;
		if ((SYNOCPLD_READ(48 + 2) & 0xf0) != v)
			goto cpld_done;
		version = (v >> 4) & 7;

		/*
		 * Try to determine whether it is a 207-style or 209-style
		 * CPLD register set, by turning the fan off and check if
		 * either bit 5 or bit 4 changes from 0 to 1 to indicate
		 * the fan is stopped.
		 */
		status = SYNOCPLD_READ(3) & 0xf0;
		SYNOCPLD_WRITE(3, 0x00);	/* fan off */

		for (i = 0; i < SYNO_FAN_TIMEOUT * 100; i++) {
			delay(10);
			v = SYNOCPLD_READ(3) & 0xf0;
			if ((status & 0x20) == 0 && (v & 0x20) != 0) {
				/* set x07 model */
				v = SYNOCPLD_READ(1) >> 6;
				model_name = models207[v];
				cpld = BI_MODEL_CPLD207;
				/* XXXX DS107v2/v3 have no thermal sensor */
				flags |= BI_MODEL_THERMAL;
				break;
			}
			if ((status & 0x10) == 0 && (v & 0x10) != 0) {
				/* set x09 model */
				v = SYNOCPLD_READ(1) >> 7;
				model_name = models209[v];
				cpld = BI_MODEL_CPLD209;
				if (v == 1)	/* DS209j */
					flags |= BI_MODEL_THERMAL;
				break;
			}
			/* XXX What about DS108j? Does it have a CPLD? */
		}

		/* turn the fan on again */
		SYNOCPLD_WRITE(3, status);

		if (i >= SYNO_FAN_TIMEOUT * 100)
			goto cpld_done;		/* timeout: no valid CPLD */
	} else {
		if (SYNOCPLD_READ(16 + 0) != v)
			goto cpld4bits;

		/* 8-bit registers assumed, make LEDs solid yellow */
		SYNOCPLD_WRITE(0, 0x55);
		v = SYNOCPLD_READ(0);
		if (v != 0x55)
			goto cpld4bits;		/* try 4 bits instead */
		if (SYNOCPLD_READ(32 + 0) != v)
			goto cpld_done;

		v = SYNOCPLD_READ(2);
		if (SYNOCPLD_READ(48 + 2) != v)
			goto cpld_done;
		version = v & 3;

		if ((v & 0x0c) != 0x0c) {
			/* set 406 model */
			model_name = models406[(v >> 2) & 3];
			cpld = BI_MODEL_CPLD406;
		} else {
			/* set 407 model */
			model_name = models407[v >> 6];
			cpld = BI_MODEL_CPLD407;
			flags |= BI_MODEL_THERMAL;
		}
	}

	printf("CPLD V%s%u detected for model %s\n",
	    cpld < BI_MODEL_CPLD406 ? "" : "1.",
	    version, model_name);

	if (cpld ==  BI_MODEL_CPLD406 || cpld ==  BI_MODEL_CPLD407) {
		/*
		 * CS/RS stations power-up their disks one after another.
		 * We have to watch over the current power state in a CPLD
		 * register, until all disks become available.
		 */
		do {
			delay(1000 * 1000);
			v = SYNOCPLD_READ(1);
			printf("Power state: %02x\r", v);
		} while (v != 0xff);
		putchar('\n');
	} else if (model_name != NULL && model_name[2] == '2') {
		/*
		 * DS207 and DS209 have a second SATA disk, which is started
		 * with several seconds delay, but no CPLD register to
		 * monitor the power state. So all we can do is to
		 * wait some more seconds during SATA-init.
		 * Also wait some seconds now, to make sure the first
		 * disk is ready after a cold start.
		 */
		sata_delay[1] = SYNO_DISK_DELAY;
		delay(10 * 1024 * 1024);
	}

  cpld_done:
	if (model_name != NULL) {
		snprintf(bi_model.name, sizeof(bi_model.name), "%s", model_name);
		bi_model.flags = cpld | version | flags;
	} else
		printf("No CPLD found. DS101/DS106.\n");
}

void
synolaunch(struct brdprop *brd)
{
	extern struct btinfo_model bi_model;
	struct dkdev_ata *sata1, *sata2;
	unsigned cpld;

	cpld = bi_model.flags & BI_MODEL_CPLD_MASK;

	if (cpld ==  BI_MODEL_CPLD406 || cpld ==  BI_MODEL_CPLD407) {
		/* set drive LEDs for active disk drives on CS/RS models */
		sata1 = lata[0].drv;
		sata2 = lata[1].drv;
		SYNOCPLD_WRITE(0, (sata1->presense[0] ? 0x80 : 0xc0) |
		    (sata1->presense[1] ? 0x20 : 0x30) |
		    (sata2->presense[0] ? 0x08 : 0x0c) |
		    (sata2->presense[1] ? 0x02 : 0x03));
	} else if (cpld ==  BI_MODEL_CPLD207 || cpld ==  BI_MODEL_CPLD209) {
		/* set drive LEDs for DS207 and DS209 models */
		sata1 = lata[0].drv;
		SYNOCPLD_WRITE(0, (sata1->presense[0] ? 0x80 : 0xc0) |
		    (sata1->presense[1] ? 0x20 : 0x30));
	}
}

void
synoreset()
{

	send_sat("C");
	/*NOTREACHED*/
}

void
qnapbrdfix(struct brdprop *brd)
{

	init_uart(uart2base, 19200, LCR_8BITS | LCR_PNONE);
	/* beep, status LED red */
	send_sat("PW");
}

void
qnapreset()
{

	send_sat("f");
	/*NOTREACHED*/
}

void
iomegabrdfix(struct brdprop *brd)
{

	init_uart(uart2base, 9600, LCR_8BITS | LCR_PNONE);
	/* LED flashing blue, fan auto, turn on at 50C, turn off at 45C */
	send_iomega('b', 'd', 2, 'a', 50, 45);
}

void
iomegareset()
{

	send_iomega('g', 0, 0, 0, 0, 0);
	/*NOTREACHED*/
}

void
dlinkbrdfix(struct brdprop *brd)
{

	init_uart(uart2base, 9600, LCR_8BITS | LCR_PNONE);
	send_sat("SYN\n");
	send_sat("ZWO\n");	/* power LED solid on */
}

void
nhnasbrdfix(struct brdprop *brd)
{

	/* status LED off, USB-LEDs on, low-speed fan */
	NHGPIO_WRITE(0x04);
}

void
nhnasreset()
{

	/* status LED on, assert system-reset to all devices */
	NHGPIO_WRITE(0x02);
	delay(100000);
	/*NOTREACHED*/
}

void
kurot4brdfix(struct brdprop *brd)
{

	init_uart(uart2base, 38400, LCR_8BITS | LCR_PEVEN);
}

void
_rtt(void)
{
	uint32_t msr;

	netif_shutdown_all();

	if (brdprop->reset != NULL)
		(*brdprop->reset)();
	else {
		msr = mfmsr();
		msr &= ~PSL_EE;
		mtmsr(msr);
		asm volatile ("sync; isync");
		asm volatile("mtspr %0,%1" : : "K"(81), "r"(0));
		msr &= ~(PSL_ME | PSL_DR | PSL_IR);
		mtmsr(msr);
		asm volatile ("sync; isync");
		run(0, 0, 0, 0, (void *)0xFFF00100); /* reset entry */
	}
	__unreachable();
}

satime_t
getsecs(void)
{
	uint64_t tb = mftb();

	return (tb / ticks_per_sec);
}

/*
 * Wait for about n microseconds (at least!).
 */
void
delay(unsigned n)
{
	uint64_t tb;
	uint32_t scratch, tbh, tbl;

	tb = mftb();
	tb += ((uint64_t)n * 1000 + ns_per_tick - 1) / ns_per_tick;
	tbh = tb >> 32;
	tbl = tb;
	asm volatile(
	    "1:	mftbu %0;"
	    "	cmpw %0,%1;"
	    "	blt 1b;"
	    "	bgt 2f;"
	    "	mftb %0;"
	    "	cmpw 0, %0,%2;"
	    "	blt 1b;"
	    "2:"
	    : "=&r"(scratch)
	    : "r"(tbh), "r"(tbl)
	    : "cc");
}

void
_wb(uint32_t adr, uint32_t siz)
{
	uint32_t bnd;

	asm volatile("eieio" ::: "memory");
	for (bnd = adr + siz; adr < bnd; adr += dcache_line_size)
		asm volatile("dcbst 0,%0" :: "r"(adr) : "memory");
	asm volatile("sync" ::: "memory");
}

void
_wbinv(uint32_t adr, uint32_t siz)
{
	uint32_t bnd;

	asm volatile("eieio" ::: "memory");
	for (bnd = adr + siz; adr < bnd; adr += dcache_line_size)
		asm volatile("dcbf 0,%0" :: "r"(adr) : "memory");
	asm volatile("sync");
}

void
_inv(uint32_t adr, uint32_t siz)
{
	uint32_t bnd, off;

	off = adr & (dcache_line_size - 1);
	adr -= off;
	siz += off;
	asm volatile("eieio" ::: "memory");
	if (off != 0) {
		/* wbinv() leading unaligned dcache line */
		asm volatile("dcbf 0,%0" :: "r"(adr) : "memory");
		if (siz < dcache_line_size)
			goto done;
		adr += dcache_line_size;
		siz -= dcache_line_size;
	}
	bnd = adr + siz;
	off = bnd & (dcache_line_size - 1);
	if (off != 0) {
		/* wbinv() trailing unaligned dcache line */
		asm volatile("dcbf 0,%0" :: "r"(bnd) : "memory"); /* it's OK */
		if (siz < dcache_line_size)
			goto done;
		siz -= off;
	}
	for (bnd = adr + siz; adr < bnd; adr += dcache_line_size) {
		/* inv() intermediate dcache lines if ever */
		asm volatile("dcbi 0,%0" :: "r"(adr) : "memory");
	}
  done:
	asm volatile("sync" ::: "memory");
}

static inline uint32_t
mfmsr(void)
{
	uint32_t msr;

	asm volatile ("mfmsr %0" : "=r"(msr));
	return msr;
}

static inline void
mtmsr(uint32_t msr)
{
	asm volatile ("mtmsr %0" : : "r"(msr));
}

static inline uint32_t
cputype(void)
{
	uint32_t pvr;

	asm volatile ("mfpvr %0" : "=r"(pvr));
	return pvr >> 16;
}

static inline uint64_t
mftb(void)
{
	uint32_t scratch;
	uint64_t tb;

	asm ("1: mftbu %0; mftb %0+1; mftbu %1; cmpw %0,%1; bne 1b"
	    : "=r"(tb), "=r"(scratch) :: "cc");
	return tb;
}

static void
init_uart(unsigned base, unsigned speed, uint8_t lcr)
{
	unsigned div;

	div = busclock / speed / 16;
	UART_WRITE(base, LCR, 0x80);		/* turn on DLAB bit */
	UART_WRITE(base, FCR, 0x00);
	UART_WRITE(base, DMB, div >> 8);	/* set speed */
	UART_WRITE(base, DLB, div & 0xff);
	UART_WRITE(base, LCR, lcr);
	UART_WRITE(base, FCR, 0x07);		/* FIFO on, TXRX FIFO reset */
	UART_WRITE(base, IER, 0x00);		/* make sure INT disabled */
}

/* talk to satellite processor */
static void
send_sat(char *msg)
{
	unsigned savedbase;

	savedbase = uart1base;
	uart1base = uart2base;
	while (*msg)
		putchar(*msg++);
	uart1base = savedbase;
}

#ifdef DEBUG
static void
iomega_debug(const char *txt, uint8_t buf[])
{
	int i;

	printf("%s:", txt);
	for (i = 0; i < IOMEGA_PACKETSIZE; i++)
		printf(" %02x", buf[i]);
	putchar('\n');
}
#endif /* DEBUG */

static void
send_iomega(int power, int led, int rate, int fan, int high, int low)
{
	uint8_t buf[IOMEGA_PACKETSIZE];
	unsigned i, savedbase;

	savedbase = uart1base;
	uart1base = uart2base;

	/* first flush the receive buffer */
  again:
	while (tstchar())
		(void)getchar();
	delay(20000);
	if (tstchar())
		goto again;
	/*
	 * Now synchronize the transmitter by sending 0x00
	 * until we receive a status reply.
	 */
	do {
		putchar(0);
		delay(50000);
	} while (!tstchar());

	for (i = 0; i < IOMEGA_PACKETSIZE; i++)
		buf[i] = getchar();
#ifdef DEBUG
	uart1base = savedbase;
	iomega_debug("68HC908 status", buf);
	uart1base = uart2base;
#endif

	/* send command */
	buf[IOMEGA_POWER] = power;
	buf[IOMEGA_LED] = led;
	buf[IOMEGA_FLASH_RATE] = rate;
	buf[IOMEGA_FAN] = fan;
	buf[IOMEGA_HIGH_TEMP] = high;
	buf[IOMEGA_LOW_TEMP] = low;
	buf[IOMEGA_ID] = 7;	/* host id */
	buf[IOMEGA_CHECKSUM] = (buf[IOMEGA_POWER] + buf[IOMEGA_LED] +
	    buf[IOMEGA_FLASH_RATE] + buf[IOMEGA_FAN] +
	    buf[IOMEGA_HIGH_TEMP] + buf[IOMEGA_LOW_TEMP] +
	    buf[IOMEGA_ID]) & 0x7f;
#ifdef DEBUG
	uart1base = savedbase;
	iomega_debug("G2 sending", buf);
	uart1base = uart2base;
#endif
	for (i = 0; i < IOMEGA_PACKETSIZE; i++)
		putchar(buf[i]);

	/* receive the reply */
	for (i = 0; i < IOMEGA_PACKETSIZE; i++)
		buf[i] = getchar();
#ifdef DEBUG
	uart1base = savedbase;
	iomega_debug("68HC908 reply", buf);
	uart1base = uart2base;
#endif

	if (buf[0] == '#')
		goto again;  /* try again on error */
	uart1base = savedbase;
}

void
putchar(int c)
{
	unsigned timo, lsr;

	if (c == '\n')
		putchar('\r');

	timo = 0x00100000;
	do {
		lsr = UART_READ(uart1base, LSR);
	} while (timo-- > 0 && (lsr & LSR_THRE) == 0);
	if (timo > 0)
		UART_WRITE(uart1base, THR, c);
}

int
getchar(void)
{
	unsigned lsr;

	do {
		lsr = UART_READ(uart1base, LSR);
	} while ((lsr & LSR_DRDY) == 0);
	return UART_READ(uart1base, RBR);
}

int
tstchar(void)
{

	return (UART_READ(uart1base, LSR) & LSR_DRDY) != 0;
}

#define SAR_MASK 0x0ff00000
#define SAR_SHIFT    20
#define EAR_MASK 0x30000000
#define EAR_SHIFT    28
#define AR(v, s) ((((v) & SAR_MASK) >> SAR_SHIFT) << (s))
#define XR(v, s) ((((v) & EAR_MASK) >> EAR_SHIFT) << (s))
static void
set_mem_bounds(unsigned tag, unsigned bk_en, ...)
{
	unsigned mbst, mbxst, mben, mbxen;
	unsigned start, end;
	va_list ap;
	int i, sh;

	va_start(ap, bk_en);
	mbst = mbxst = mben = mbxen = 0;

	for (i = 0; i < 4; i++) {
		if ((bk_en & (1U << i)) != 0) {
			start = va_arg(ap, unsigned);
			end = va_arg(ap, unsigned);
		} else {
			start = 0x3ff00000;
			end = 0x3fffffff;
		}
		sh = i << 3;
		mbst |= AR(start, sh);
		mbxst |= XR(start, sh);
		mben |= AR(end, sh);
		mbxen |= XR(end, sh);
	}
	va_end(ap);

	pcicfgwrite(tag, MPC106_MEMSTARTADDR1, mbst);
	pcicfgwrite(tag, MPC106_EXTMEMSTARTADDR1, mbxst);
	pcicfgwrite(tag, MPC106_MEMENDADDR1, mben);
	pcicfgwrite(tag, MPC106_EXTMEMENDADDR1,	mbxen);
	pcicfgwrite(tag, MPC106_MEMEN,
	    (pcicfgread(tag, MPC106_MEMEN) & ~0xff) | (bk_en & 0xff));
}

static unsigned
mpc107memsize(void)
{
	unsigned bankn, end, n, tag, val;

	tag = pcimaketag(0, 0, 0);

	if (brdtype == BRD_ENCOREPP1) {
		/* the brd's PPCBOOT looks to have erroneous values */
		set_mem_bounds(tag, 1, 0x00000000, (128 << 20) - 1);
	} else if (brdtype == BRD_NH230NAS) {
		/*
		 * PPCBoot sets the end address to 0x7ffffff, although the
		 * board has just 64MB (0x3ffffff).
		 */
		set_mem_bounds(tag, 1, 0x00000000, 0x03ffffff);
	}

	bankn = 0;
	val = pcicfgread(tag, MPC106_MEMEN);
	for (n = 0; n < 4; n++) {
		if ((val & (1U << n)) == 0)
			break;
		bankn = n;
	}
	bankn <<= 3;

	val = pcicfgread(tag, MPC106_EXTMEMENDADDR1);
	end =  ((val >> bankn) & 0x03) << 28;
	val = pcicfgread(tag, MPC106_MEMENDADDR1);
	end |= ((val >> bankn) & 0xff) << 20;
	end |= 0xfffff;

	return (end + 1); /* assume the end address matches total amount */
}

struct fis_dir_entry {
	char		name[16];
	uint32_t	startaddr;
	uint32_t	loadaddr;
	uint32_t	flashsize;
	uint32_t	entryaddr;
	uint32_t	filesize;
	char		pad[256 - (16 + 5 * sizeof(uint32_t))];
};

#define FIS_LOWER_LIMIT	0xfff00000

/*
 * Look for a Redboot-style Flash Image System FIS-directory and
 * return a pointer to the start address of the requested file.
 */
static void *
redboot_fis_lookup(const char *filename)
{
	static const char FISdirname[16] = {
	    'F', 'I', 'S', ' ',
	    'd', 'i', 'r', 'e', 'c', 't', 'o', 'r', 'y', 0, 0, 0
	};
	struct fis_dir_entry *dir;

	/*
	 * The FIS directory is usually in the last sector of the flash.
	 * But we do not know the sector size (erase size), so start
	 * at 0xffffff00 and scan backwards in steps of the FIS directory
	 * entry size (0x100).
	 */
	for (dir = (struct fis_dir_entry *)0xffffff00;
	    (uint32_t)dir >= FIS_LOWER_LIMIT; dir--)
		if (memcmp(dir->name, FISdirname, sizeof(FISdirname)) == 0)
			break;
	if ((uint32_t)dir < FIS_LOWER_LIMIT) {
		printf("No FIS directory found!\n");
		return NULL;
	}

	/* Now find filename by scanning the directory from beginning. */
	dir = (struct fis_dir_entry *)dir->startaddr;
	while (dir->name[0] != 0xff && (uint32_t)dir < 0xffffff00) {
		if (strcmp(dir->name, filename) == 0)
			return (void *)dir->startaddr;	/* found */
		dir++;
	}
	printf("\"%s\" not found in FIS directory!\n", filename);
	return NULL;
}

static void
read_mac_string(uint8_t *mac, char *p)
{
	int i;

	for (i = 0; i < 6; i++, p += 3)
		*mac++ = read_hex(p);
}

/*
 * Scan through the Flash memory and look for a string starting at 512 bytes
 * block boundaries, matching the format: xx:xx:xx:xx:xx:xx<NUL>, where "x"
 * are hexadecimal digits.
 * Read the first match as our MAC address.
 * The start address of the search, p, *must* be dividable by 512!
 * Return false when no suitable MAC string was found.
 */
static int
find_mac_string(uint8_t *mac, char *p)
{
	int i;

	for (;;) {
		for (i = 0; i < 3 * 6; i += 3) {
			if (!isxdigit((unsigned)p[i]) ||
			    !isxdigit((unsigned)p[i + 1]))
				break;
			if ((i < 5 && p[i + 2] != ':') ||
			    (i >= 5 && p[i + 2] != '\0'))
				break;
		}
		if (i >= 6) {
			/* found a valid MAC address */
			read_mac_string(mac, p);
			return 1;
		}
		if (p >= (char *)0xfffffe00)
			break;
		p += 0x200;
	}
	return 0;
}


/*
 * For cost saving reasons some NAS boxes lack SEEPROM for NIC's
 * ethernet address and keep it in their Flash memory instead.
 */
void
read_mac_from_flash(uint8_t *mac)
{
	uint8_t *p;

	switch (brdtype) {
	case BRD_SYNOLOGY:
		p = redboot_fis_lookup("vendor");
		if (p == NULL)
			break;
		memcpy(mac, p, 6);
		return;
	case BRD_DLINKDSM:
		read_mac_string(mac, (char *)0xfff0ff80);
		return;
	case BRD_QNAPTS:
		if (find_mac_string(mac, (char *)0xfff00000))
			return;
		break;
	default:
		printf("Warning: This board has no known method defined "
		    "to determine its MAC address!\n");
		break;
	}

	/* set to 00:00:00:00:00:00 in case of error */
	memset(mac, 0, 6);
}

#ifdef DEBUG
void
sat_write(char *p, int len)
{
	unsigned savedbase;

	savedbase = uart1base;
	uart1base = uart2base;
	while (len--)
		putchar(*p++);
	uart1base = savedbase;
}

int
sat_getch(void)
{
	unsigned lsr;

	do {
		lsr = UART_READ(uart2base, LSR);
	} while ((lsr & LSR_DRDY) == 0);
	return UART_READ(uart2base, RBR);
}

int
sat_tstch(void)
{

	return (UART_READ(uart2base, LSR) & LSR_DRDY) != 0;
}
#endif /* DEBUG */
