/* $Id: afbinit.c,v 1.1.1.1 1999/09/08 06:42:36 davem Exp $
 * afbinit.c: Init AFB for general purpose usage at system
 *	      boot.  Actually all this program does is
 *	      load the firmware ucode needed on the AFB_FLOAT
 *	      chips.
 *
 * Copyright (C) 1999 David S. Miller (davem@redhat.com)
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

/* Define this to debug the microcode loading procedure. */
#undef DEBUG_UCODE_LOAD

/* Default microcode file name if none explicitly specified. */
#define	UCODE_FILE	"/usr/lib/afb.ucode"

/* In order to make this quick hack completely self-contained, I
 * hardcode the AFB register offsets with these macros.
 */
#define AFB_UREG_UCSR(uregs)	((volatile unsigned int *)(((char *)uregs) + 0x0900))
#define AFB_UREG_FEM(uregs)	((volatile unsigned int *)(((char *)uregs) + 0x1540))
#define AFB_UREG_SRAMAR(uregs)	((volatile unsigned int *)(((char *)uregs) + 0x1550))
#define AFB_KREG_SRAM36(kregs)	(((char *)kregs) + 0x14c0)
#define AFB_KREG_ASCR(kregs)	((volatile unsigned int *)(((char *)kregs) + 0x0800))
#define AFB_ASCR_RESTART	0x00040000
#define AFB_ASCR_STOP		0x00020000
#define AFB_KREG_KCSR(kregs)	((volatile unsigned int *)(((char *)kregs) + 0x0900))

#define FFB_UCSR_ALL_BUSY	0x03000000
#define FFBFifo(uregs, __n)	\
do {	void *tmp = (uregs);	\
	while(((*AFB_UREG_UCSR(tmp) & 0xfff) - 8) < (__n))	\
		__asm__ __volatile__("" : : : "memory");	\
} while(0)
#define FFBWait(uregs)	\
do {	void *tmp = (uregs); \
	while(*(AFB_UREG_UCSR(tmp)) & FFB_UCSR_ALL_BUSY) \
		__asm__ __volatile__("membar	#Sync" : : : "memory"); \
} while(0)

static void sandblast_sram(char *ucodep, int blocks, void *afb_uregs, void *afb_kregs)
{
	char *sram = AFB_KREG_SRAM36(afb_kregs);

	/* Reset SRAM address. */
	*(AFB_UREG_SRAMAR(afb_uregs)) = 0;

	/* Poke it. */
	while(blocks--) {
		FFBFifo(afb_uregs, 16);
		__asm__ __volatile__("ld	[%0 + 0x00], %%f1\n\t"
				     "ld	[%0 + 0x04], %%f0\n\t"
				     "ld	[%0 + 0x08], %%f3\n\t"
				     "ld	[%0 + 0x0c], %%f2\n\t"
				     "ld	[%0 + 0x10], %%f5\n\t"
				     "ld	[%0 + 0x14], %%f4\n\t"
				     "ld	[%0 + 0x18], %%f7\n\t"
				     "ld	[%0 + 0x1c], %%f6\n\t"
				     "ld	[%0 + 0x20], %%f9\n\t"
				     "ld	[%0 + 0x24], %%f8\n\t"
				     "ld	[%0 + 0x28], %%f11\n\t"
				     "ld	[%0 + 0x2c], %%f10\n\t"
				     "ld	[%0 + 0x30], %%f13\n\t"
				     "ld	[%0 + 0x34], %%f12\n\t"
				     "ld	[%0 + 0x38], %%f15\n\t"
				     "ld	[%0 + 0x3c], %%f14\n\t"
				     "membar	#Sync\n\t"
				     "stda	%%f0, [%1] 240\n\t"
				     "membar	#Sync"
				     : : "r" (ucodep), "r" (sram));
		ucodep += 64;
	}

	/* Wait for the chip to eat it. */
	FFBWait(afb_uregs);

	/* Read it back to ensure completion. */
	__asm__ __volatile__("ldda	[%0] 240, %%f0\n\t"
			     "membar	#Sync"
			     : : "r" (sram));
}

/* Note, if you try to enable a non-existant float chip
 * this will lock up the chip.
 */
static void afb_ucode_upload(char *ucodep, int nblocks, void *uregs, void *kregs)
{
	unsigned int all_floats = *(AFB_KREG_ASCR(kregs)) & 0x3f;
	unsigned int orig_fem;

	/* Idle raster processor.  Touching the float enable mask
	 * while the floats are busy can flatline the machine.
	 */
	FFBWait(uregs);

	/* Set all valid float enable bits. */
#ifdef DEBUG_UCODE_LOAD
	printf("\nSet FEM %08x\n", all_floats);
	fflush(stdout);
#endif
	*(AFB_UREG_FEM(uregs)) = all_floats;

	/* Stop all floats. */
#ifdef DEBUG_UCODE_LOAD
	printf("Set ASCR %08x\n", AFB_ASCR_STOP);
	fflush(stdout);
#endif
	*(AFB_KREG_ASCR(kregs)) = AFB_ASCR_STOP;

	/* Idle again, just to be sure. */
#ifdef DEBUG_UCODE_LOAD
	printf("Idle RP\n");
	fflush(stdout);
#endif
	FFBWait(uregs);

	/* Ok, we should be in a state where it is safe to write
	 * into the SRAM of the float processors.
	 */
	if (all_floats & ~1) {
		/* Enable all floats except float zero. */
#ifdef DEBUG_UCODE_LOAD
		printf("Set FEM %08x\n", (all_floats & ~1) & 0x3f);
		fflush(stdout);
#endif
		*(AFB_UREG_FEM(uregs)) = (all_floats & ~1) & 0x3f;

		/* Load ucode into SRAM. */
#ifdef DEBUG_UCODE_LOAD
		printf("Write SRAM\n");
		fflush(stdout);
#endif
		sandblast_sram(ucodep, nblocks, uregs, kregs);
	}

	/* Now, enable _only_ float zero. */
#ifdef DEBUG_UCODE_LOAD
	printf("Set FEM %08x\n", (all_floats & 1));
	fflush(stdout);
#endif
	*(AFB_UREG_FEM(uregs)) = (all_floats & 1);

	/* Load ucode again. */
#ifdef DEBUG_UCODE_LOAD
	printf("Write SRAM\n");
	fflush(stdout);
#endif
	sandblast_sram(ucodep, nblocks, uregs, kregs);

	/* No we must get the floats going once more.
	 * First enable all floats again.
	 */
#ifdef DEBUG_UCODE_LOAD
	printf("Set FEM %08x\n", all_floats);
	fflush(stdout);
#endif
	*(AFB_UREG_FEM(uregs)) = all_floats;

	/* Kick the ASCR to restart them. */
#ifdef DEBUG_UCODE_LOAD
	printf("Set ASCR %08x\n", AFB_ASCR_RESTART);
	fflush(stdout);
#endif
	*(AFB_KREG_ASCR(kregs)) = AFB_ASCR_RESTART;

	/* Idle FBC/RP once more... */
#ifdef DEBUG_UCODE_LOAD
	printf("Idle RP\n");
	fflush(stdout);
#endif
	FFBWait(uregs);

#ifdef DEBUG_UCODE_LOAD
	printf("Done!!!!\n");
	fflush(stdout);
#endif
}

static void usage(char *me)
{
	printf("Usage:	%s /dev/fb[0123] [ucode_file]\n", me);
	exit(1);
}

int main(int argc, char **argp)
{
	struct afb_ucode_header {
		char ident[8];
		unsigned int ucode_words;
		unsigned int __unused[2];
	} ucheader;
	unsigned int *ucode;
	int afb_fd, ucode_fd, ucode_version;
	void *uregs, *kregs;
	char *afb_fname;
	char *ucode_fname = UCODE_FILE;

	if(argc != 2 && argc != 3)
		usage(argp[0]);
	afb_fname = argp[1];
	if(argc == 3)
		ucode_fname = argp[2];

	afb_fd = open(afb_fname, O_RDWR);
	if(afb_fd == -1) {
		perror("Open AFB device");
		exit(1);
	}
	ucode_fd = open(ucode_fname, O_RDONLY);
	if(ucode_fd == -1) {
		perror("Open AFB ucode file");
		exit(1);
	}
	memset(&ucheader, 0, sizeof(ucheader));
	if(read(ucode_fd, &ucheader, sizeof(ucheader)) == -1) {
		perror("Read UCODE header");
		exit(1);
	}
	ucode = (unsigned int *)malloc(ucheader.ucode_words << 2);
	if(ucode == NULL) {
		fprintf(stderr, "Cannot malloc %d bytes for UCODE.\n",
			ucheader.ucode_words << 2);
		exit(1);
	}
	if(read(ucode_fd, ucode, ucheader.ucode_words << 2) == -1) {
		perror("Read UCODE contents");
		exit(1);
	}

	/* MMAP the registers. */
	uregs = mmap(0, 0x2000,
		     PROT_READ | PROT_WRITE,
		     MAP_PRIVATE,
		     afb_fd,
		     0x04000000);
	if (uregs == (void *)-1L) {
		perror("mmap user regs");
		exit(1);
	}

	kregs = mmap(0, 0x2000,
		     PROT_READ | PROT_WRITE,
		     MAP_PRIVATE,
		     afb_fd,
		     0x0bc04000);
	if (kregs == (void *)-1L) {
		perror("mmap kernel regs");
		exit(1);
	}

	/* Say what UCODE version we are loading. */
	ucode_version = *(ucode + (0x404 / sizeof(unsigned int)));
	printf("Revision-%d.%d.%d ",
	       (ucode_version >> 16) & 0xff,
	       (ucode_version >>  8) & 0xff,
	       (ucode_version >>  0) & 0xff);

	afb_ucode_upload((char *)ucode, ucheader.ucode_words / 16, uregs, kregs);

	munmap(kregs, 0x2000);
	munmap(uregs, 0x2000);

	close(ucode_fd);
	close(afb_fd);

	exit(0);
}