/* $Id: sbus.c,v 1.3 1998/03/03 08:41:42 jj Exp $
 * sbus.c: Probe for Sun SBUS and UPA framebuffers using OpenPROM.
 *
 * Copyright (C) 1998 Jakub Jelinek (jj@ultra.linux.cz)
 *
 *  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 <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/utsname.h>

#include "sbusprobe.h"

#ifdef __sparc__

#include <asm/openpromio.h>

static char *promdev = "/dev/openprom";
static int promfd;
static int prom_root_node, prom_current_node;
#define MAX_PROP	128
#define MAX_VAL		(4096-128-4)
static char buf[4096];
#define DECL_OP(size) struct openpromio *op = (struct openpromio *)buf; op->oprom_size = (size)

static int prom_getsibling(int node)
{
	DECL_OP(sizeof(int));
	
	if (node == -1) return 0;
	*(int *)op->oprom_array = node;
	if (ioctl (promfd, OPROMNEXT, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

static int prom_getchild(int node)
{
	DECL_OP(sizeof(int));
	
	if (!node || node == -1) return 0;
	*(int *)op->oprom_array = node;
	if (ioctl (promfd, OPROMCHILD, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

static char *prom_getproperty(char *prop, int *lenp)
{
	DECL_OP(MAX_VAL);
	
	strcpy (op->oprom_array, prop);
	if (ioctl (promfd, OPROMGETPROP, op) < 0)
		return 0;
	if (lenp) *lenp = op->oprom_size;
	return op->oprom_array;
}

static int prom_getbool(char *prop)
{
	DECL_OP(0);

	*(int *)op->oprom_array = 0;
	for (;;) {
		op->oprom_size = MAX_PROP;
	        if (ioctl(promfd, OPROMNXTPROP, op) < 0)
	        	return 0;
	        if (!op->oprom_size)
	        	return 0;
	        if (!strcmp (op->oprom_array, prop))
	        	return 1;
	}
}

static int sbus_ndevs;
static enum sbusClass sbus_class;
static struct sbusDevice ***sbus_devs;

/* create a new clean sbusDevice entry */
struct sbusDevice *sbusNewDevice( void ) {
    struct sbusDevice *p;

    p = (struct sbusDevice *) malloc( sizeof(struct sbusDevice) );
    p->nhits = 1;
    p->name=malloc(sizeof(char *));
    p->module=malloc(sizeof(char *));
    p->fb=NULL;
    p->class=SBUS_UNSET;
    return p;
}

/* remove a sbusDevice */
void sbusFreeDevice( struct sbusDevice *p ) {
    if (p->name) {
	free(p->name[0]);
	free(p->name);
    }

    if (p->module) {
	free(p->module[0]);
	free(p->module);
    }

    if (p->fb) {
	free(p->fb);
    }

    free(p);
}

static void prom_walk(int node, int sbus)
{
	int nextnode;
	int len, nsbus = sbus;
	char *prop = prom_getproperty("device_type", &len);
	char *type = NULL, *module;
	int depth, width = 1152, height = 900;
	int freq = 0, sense = -1;
	
	if (!prop || len <= 0)
		goto next;
	switch (sbus_class) {
	case SBUS_ETHERNET: 
		if (strcmp(prop, "network"))
			goto next;
		prop = prom_getproperty("name", &len);
		if (!prop || len <= 0)
			goto next;
		while ((*prop >= 'A' && *prop <= 'Z') || *prop == ',') prop++;
		if (!sbus) goto next;
		if (!strcmp(prop, "hme")) {
			type = "Sun Happy Meal Ethernet";
			module = "sunhme";
		} else if (!strcmp(prop, "le")) {
			type = "Sun Lance Ethernet";
			module = "";
		} else if (!strcmp(prop, "qe")) {
			prop = prom_getproperty("channel#", &len);
			if (prop && len == 4 && *(int *)prop)
				goto next;
			type = "Sun Quad Ethernet";
			module = "sunqe";
		} else if (!strcmp(prop, "mlanai") || !strcmp(prop, "myri")) {
			type = "MyriCOM MyriNET Gigabit Ethernet";
			module = "myri_sbus";
		}
		break;
	case SBUS_SCSI:
		depth = strcmp(prop, "scsi");
		prop = prom_getproperty("name", &len);
		if (!prop || len <= 0)
			goto next;
		while ((*prop >= 'A' && *prop <= 'Z') || *prop == ',') prop++;
		if (!sbus) goto next;
		if (depth && strcmp(prop, "soc"))
			goto next;
		if (!strcmp(prop, "soc")) {
			type = "Sun SPARCStorage Array";
			module = "fc4:soc:pluto";
		} else if (!strcmp(prop, "esp")) {
			type = "Sun Enhanced SCSI Processor (ESP)";
			module = "";
		} else if (!strcmp(prop, "fas")) {
			type = "Sun Swift (ESP)";
			module = "";
		} else if (!strcmp(prop, "ptisp")) {
			type = "Performance Technologies ISP";
			module = "qlogicpti";
		} else if (!strcmp(prop, "isp")) {
			type = "QLogic ISP";
			module = "qlogicpti";
		}
		break;
	case SBUS_VIDEO: 
		if (strcmp(prop, "display"))
			goto next;
		prop = prom_getproperty("name", &len);
		if (!prop || len <= 0)
			goto next;
		while ((*prop >= 'A' && *prop <= 'Z') || *prop == ',') prop++;
		if (!sbus && strcmp(prop, "ffb") && strcmp(prop, "afb") &&
		    strcmp(prop, "cgfourteen"))
			goto next;
		if (!strcmp(prop, "bwtwo")) {
			type = "Sun Monochrome (bwtwo)";
			depth = 1;
		} else if (!strcmp(prop, "cgthree")) {
			type = "Sun Color3 (cgthree)";
			depth = 8;
		} else if (!strcmp(prop, "cgeight")) {
			type = "Sun CG8/RasterOps";
			depth = 8;
		} else if (!strcmp(prop, "cgtwelve")) {
			type = "Sun GS (cgtwelve)";
			depth = 24;
		} else if (!strcmp(prop, "gt")) {
			type = "Sun Graphics Tower";
			depth = 24;
		} else if (!strcmp(prop, "mgx")) {
			prop = prom_getproperty("fb_size", &len);
			if (prop && len == 4 && *(int *)prop == 0x400000)
				type = "Quantum 3D MGXplus with 4M VRAM";
			else
				type = "Quantum 3D MGXplus";
			depth = 24;
		} else if (!strcmp(prop, "cgsix")) {
			int chiprev = 0;
			int vmsize = 0;
			prop = prom_getproperty("chiprev", &len);
			if (prop && len == 4)
				chiprev = *(int *)prop;
			prop = prom_getproperty("montype", &len);
			if (prop && len == 4)
				sense = *(int *)prop;
			prop = prom_getproperty("vfreq", &len);
			if (prop && len == 4)
				freq = *(int *)prop;
			prop = prom_getproperty("vmsize", &len);
			if (prop && len == 4)
				vmsize = *(int *)prop;
			switch (chiprev) {
			case 0: type = "Sun Unknown GX"; break;
			case 1 ... 4: type = "Sun Double width GX"; break;
			case 5 ... 9: type = "Sun Single width GX"; break;
			case 11: 
				switch (vmsize) {
				case 2: type = "Sun Turbo GX with 1M VSIMM"; break;
				case 4: type = "Sun Turbo GX Plus"; break;
				default: type = "Sun Turbo GX"; break;
				}
			}
			depth = 8;
		} else if (!strcmp(prop, "cgfourteen")) {
			int size = 0;
			prop = prom_getproperty("vfreq", &len);
			if (prop && len == 4)
				freq = *(int *)prop;
			prop = prom_getproperty("reg", &len);
			if (prop && !(len % 12) && len > 0)
				size = *(int *)(prop + len - 4);
			switch (size) {
			case 0x400000: type = "Sun SX with 4M VSIMM"; break;
			case 0x800000: type = "Sun SX with 8M VSIMM"; break;
			default: type = "Sun SX";
			}
			depth = 24;
		} else if (!strcmp(prop, "leo")) {
			prop = prom_getproperty("model", &len);
			if (prop && len > 0 && !strstr(prop, "501-2503"))
				type = "Sun Turbo ZX";
			else
				type = "Sun ZX or Turbo ZX";
			depth = 24;
		} else if (!strcmp(prop, "tcx")) {
			if (prom_getbool("tcx-8-bit")) {
				type = "Sun TCX (8bit)";
				depth = 8;
			} else {
				type = "Sun TCX (S24)";
				depth = 24;
			}
		} else if (!strcmp(prop, "afb")) {
			int btype = 0;
			prop = prom_getproperty("vfreq", &len);
			if (prop && len == 4)
				freq = *(int *)prop;
			prop = prom_getproperty("board_type", &len);
			if (prop && len == 4)
				btype = *(int *)prop;
			if (btype == 3)
				type = "Sun Elite3D-M6 Horizontal";
			else
				type = "Sun Elite3D";
			depth = 24;
		} else if (!strcmp(prop, "ffb")) {
			int btype = 0;
			prop = prom_getproperty("vfreq", &len);
			if (prop && len == 4)
				freq = *(int *)prop;
			prop = prom_getproperty("board_type", &len);
			if (prop && len == 4)
				btype = *(int *)prop;
			switch (btype) {
			case 0x08: type="Sun FFB 67MHz Creator"; break;
			case 0x0b: type="Sun FFB 67MHz Creator 3D"; break;
			case 0x1b: type="Sun FFB 75MHz Creator 3D"; break;
			case 0x20:
			case 0x28: type="Sun FFB2 Vertical Creator"; break;
			case 0x23:
			case 0x2b: type="Sun FFB2 Vertical Creator 3D"; break;
			case 0x30: type="Sun FFB2+ Vertical Creator"; break;
			case 0x33: type="Sun FFB2+ Vertical Creator 3D"; break;
			case 0x40:
			case 0x48: type="Sun FFB2 Horizontal Creator"; break;
			case 0x43:
			case 0x4b: type="Sun FFB2 Horizontal Creator 3D"; break;
			default: type = "Sun FFB"; break;
			}
			depth = 24;
		}
		if (type) {
			prop = prom_getproperty("width", &len);
			if (prop && len == 4)
				width = *(int *)prop;
			prop = prom_getproperty("height", &len);
			if (prop && len == 4)
				height = *(int *)prop;
		}
	default:
		break;
	}
	if (type) {
		if ((sbus_ndevs % 4) == 0) 
			*sbus_devs=(struct sbusDevice **)
			  realloc(*sbus_devs,(sbus_ndevs+4)*sizeof(struct sbusDevice *));
		(*sbus_devs)[sbus_ndevs]=sbusNewDevice();

		if (sbus_class == SBUS_VIDEO) {
			(*sbus_devs)[sbus_ndevs]->fb = (struct sbusFB *)malloc(sizeof (struct sbusFB));
			(*sbus_devs)[sbus_ndevs]->fb->width = width;
			(*sbus_devs)[sbus_ndevs]->fb->height = height;
			(*sbus_devs)[sbus_ndevs]->fb->freq = freq;
			(*sbus_devs)[sbus_ndevs]->fb->monitor = sense;
		}
		(*sbus_devs)[sbus_ndevs]->name[0]=strdup(type);
		if (sbus_class == SBUS_VIDEO) {
			switch (depth) {
			case 1: type = "Server:SunMono"; break;
			case 8: type = "Server:Sun"; break;
			case 24: type = "Server:Sun24"; break;
			}
			(*sbus_devs)[sbus_ndevs]->module[0]=strdup(type);
		} else {
			(*sbus_devs)[sbus_ndevs]->module[0]=strdup(module);
		}
		(*sbus_devs)[sbus_ndevs]->class=sbus_class;
		sbus_ndevs++;
	}
next:
	prop = prom_getproperty("name", &len);
	if (prop && len > 0 && (!strcmp(prop, "sbus") || !strcmp(prop, "sbi")))
		nsbus = 1;
	nextnode = prom_getchild(node);
	if (nextnode)
		prom_walk(nextnode, nsbus);
	nextnode = prom_getsibling(node);
	if (nextnode)
		prom_walk(nextnode, sbus);
}
    
/* given a class to probe, returns an array of devices found which match */
/* all entries are malloc'd, so caller must free when done. Use          */
/* sbusDeviceFree() to free individual sbusDevice's, since they have     */
/* internal elements which are malloc'd                                  */
/* returns number cards found. If nothing found returns 0 and            */
/* setsdevs is set to NULL                                               */
/* if system error occurs returns -1                			 */

int sbusProbeDevice( enum sbusClass type, struct sbusDevice ***devs ) {
    sbus_ndevs = 0;
    sbus_class = type;
    sbus_devs = devs;
    *devs = NULL;
    promfd = open(promdev, O_RDONLY);
    if (promfd == -1)
    	return -1;
    prom_root_node = prom_getsibling(0);
    if (!prom_root_node)
    	return -1;

    prom_walk(prom_root_node, 0);
    close(promfd);
    return sbus_ndevs;
}

#else

int sbusProbeDevice( enum sbusClass type, struct sbusDevice ***devs ) {
    return -1;
}

struct sbusDevice *sbusNewDevice( void ) {
    return NULL;
}

void sbusFreeDevice( struct sbusDevice *p ) {
}

#endif
