/*======================================================================

    PCMCIA controller probe

    Written by David Hinds, dhinds@allegro.stanford.edu
    
======================================================================*/

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include <asm/io.h>

#include <pcmcia/config.h>
#include "i82365.h"
#include "pd67xx.h"
#include "vg468.h"
#include "tcic.h"

static const char *version =
"probe.c 1.21 1997/03/21 07:25:23 (David Hinds)\n";

static int i365_base = 0x03e0;

/*====================================================================*/

#ifdef CONFIG_PCI

typedef struct {
    char	*tag;
    char	*name;
} pci_id_t;

pci_id_t pci_id[] = {
    { "Cirrus Logic CL 6729", "Cirrus PD6729" },
    { "Vendor id=119b. Device id=1221.", "Omega Micro 82C092G" },
    { "Vendor id=1180. Device id=466.", "Ricoh RL5C466" },
    { "Vendor id=10b3. Device id=b106.", "SMC 34C90" },
    { "Vendor id=1013. Device id=1110.", "Cirrus PD6832" },
    { "Vendor id=104c. Device id=ac12.", "TI 1131" }
};
#define PCI_COUNT (sizeof(pci_id)/sizeof(pci_id_t))

static int pci_probe(int verbose, int module)
{
    char buf[4096], *name = NULL;
    int sock = 0, fd, nb, i;
    
    if (!module)
	printf("PCI bridge probe: ");

    fd = open("/proc/pci", O_RDONLY);
    if (fd != -1) {
	nb = read(fd, buf, 4096);
	buf[nb] = '\0';
	for (i = 0; i < PCI_COUNT; i++)
	    if (strstr(buf, pci_id[i].tag) != NULL)
		break;
	if (i != PCI_COUNT) {
	    name = pci_id[i].name;
	    sock = 2;
	}
	close(fd);
    }
    
    if (sock != 0) {
	if (module)
	    printf("i82365\n");
	else
	    printf("%s found, %d sockets.\n", name, sock);
	return 0;
    } else {
	if (!module)
	    printf("not found.\n");
	return -ENODEV;
    }
}
#endif

/*====================================================================*/

static u_char i365_get(u_short sock, u_short reg)
{
    u_char val = I365_REG(sock, reg);
    outb_p(val, i365_base); val = inb_p(i365_base+1);
    return val;
}

static void i365_set(u_short sock, u_short reg, u_char data)
{
    u_char val = I365_REG(sock, reg);
    outb_p(val, i365_base); outb_p(data, i365_base+1);
}

static void i365_bset(u_short sock, u_short reg, u_char mask)
{
    u_char d = i365_get(sock, reg);
    d |= mask;
    i365_set(sock, reg, d);
}

static void i365_bclr(u_short sock, u_short reg, u_char mask)
{
    u_char d = i365_get(sock, reg);
    d &= ~mask;
    i365_set(sock, reg, d);
}

/*====================================================================*/

int i365_probe(int verbose, int module)
{
    int val, sock, done;
    char *name = "i82365sl";

    if (!module)
	printf("Intel PCIC probe: ");
    if (verbose) printf("\n");
    
    sock = done = 0;
    ioperm(i365_base, 4, 1);
    ioperm(0x80, 1, 1);
    for (; sock < 2; sock++) {
	val = i365_get(sock, I365_IDENT);
	if (verbose)
	    printf("  ident(%d)=%#2.2x", sock, val); 
	switch (val) {
	case 0x82:
	    name = "i82365sl A step";
	    break;
	case 0x83:
	    name = "i82365sl B step";
	    break;
	case 0x84:
	    name = "VLSI 82C146";
	    break;
	case 0x88: case 0x89: case 0x8a:
	    name = "IBM Clone";
	    break;
	case 0x8b: case 0x8c:
	    break;
	default:
	    done = 1;
	}
	if (done) break;
    }

    if (verbose) printf("\n  ");
    if (sock == 0) {
	if (!module)
	    printf("not found.\n");
	return -ENODEV;
    }

    if ((sock == 2) && (strcmp(name, "VLSI 82C146") == 0))
	name = "i82365sl DF";

    /* Check for Vadem chips */
    outb_p(0x0e, i365_base);
    outb_p(0x37, i365_base);
    i365_bset(0, VG468_MISC, VG468_MISC_VADEMREV);
    val = i365_get(0, I365_IDENT);
    if (val & I365_IDENT_VADEM) {
	if ((val & 7) < 4)
	    name = "Vadem VG-468";
	else
	    name = "Vadem VG-469";
	i365_bclr(0, VG468_MISC, VG468_MISC_VADEMREV);
    }
    
    /* Check for Cirrus CL-PD67xx chips */
    i365_set(0, PD67_CHIP_INFO, 0);
    val = i365_get(0, PD67_CHIP_INFO);
    if ((val & PD67_INFO_CHIP_ID) == PD67_INFO_CHIP_ID) {
	val = i365_get(0, PD67_CHIP_INFO);
	if ((val & PD67_INFO_CHIP_ID) == 0) {
	    if (val & PD67_INFO_SLOTS)
		name = "Cirrus CL-PD672x";
	    else {
		name = "Cirrus CL-PD6710";
		sock = 1;
	    }
	}
    }

    if (module)
	printf("i82365\n");
    else
	printf("%s found, %d sockets.\n", name, sock);
    return 0;
    
} /* i365_probe */
  
/*====================================================================*/

static u_char tcic_getb(u_long base, u_char reg)
{
    u_char val = inb(base+reg);
    return val;
}

static void tcic_setb(u_long base, u_char reg, u_char data)
{
    outb(data, base+reg);
}

static u_short tcic_getw(u_long base, u_char reg)
{
    u_short val = inw(base+reg);
    return val;
}

static void tcic_setw(u_long base, u_char reg, u_short data)
{
    outw(data, base+reg);
}

static u_short tcic_aux_getw(u_long base, u_short reg)
{
    u_char mode = (tcic_getb(base, TCIC_MODE) & TCIC_MODE_PGMMASK) | reg;
    tcic_setb(base, TCIC_MODE, mode);
    return tcic_getw(base, TCIC_AUX);
}

static void tcic_aux_setw(u_long base, u_short reg, u_short data)
{
    u_char mode = (tcic_getb(base, TCIC_MODE) & TCIC_MODE_PGMMASK) | reg;
    tcic_setb(base, TCIC_MODE, mode);
    tcic_setw(base, TCIC_AUX, data);
}

static int get_tcic_id(u_long base)
{
    u_short id;
    tcic_aux_setw(base, TCIC_AUX_TEST, TCIC_TEST_DIAG);
    id = tcic_aux_getw(base, TCIC_AUX_ILOCK);
    id = (id & TCIC_ILOCKTEST_ID_MASK) >> TCIC_ILOCKTEST_ID_SH;
    tcic_aux_setw(base, TCIC_AUX_TEST, 0);
    return id;
}

int tcic_probe_at(u_long base, int module)
{
    int i;
    u_short old;
    
    /* Anything there?? */
    for (i = 0; i < 0x10; i += 2)
	if (tcic_getw(base, i) == 0xffff)
	    return -1;

    if (!module)
	printf("  at %#3.3lx: ", base); fflush(stdout);

    /* Try to reset the chip */
    tcic_setw(base, TCIC_SCTRL, TCIC_SCTRL_RESET);
    tcic_setw(base, TCIC_SCTRL, 0);
    
    /* Can we set the addr register? */
    old = tcic_getw(base, TCIC_ADDR);
    tcic_setw(base, TCIC_ADDR, 0);
    if (tcic_getw(base, TCIC_ADDR) != 0) {
	tcic_setw(base, TCIC_ADDR, old);
	return -2;
    }
    
    tcic_setw(base, TCIC_ADDR, 0xc3a5);
    if (tcic_getw(base, TCIC_ADDR) != 0xc3a5)
	return -3;

    return 2;
}

int tcic_probe(int verbose, int module, u_long base)
{
    int sock, id;

    if (!module)
	printf("Databook TCIC-2 probe: "); fflush(stdout);
    
    ioperm(base, 16, 1);
    ioperm(0x80, 1, 1);
    sock = tcic_probe_at(base, module);
    
    if (sock <= 0) {
	if (!module)
	    printf("not found.\n");
	return -ENODEV;
    }

    if (module)
	printf("tcic\n");
    else {
	id = get_tcic_id(base);
	switch (id) {
	case TCIC_ID_DB86082:
	    printf("DB86082"); break;
	case TCIC_ID_DB86082A:
	    printf("DB86082A"); break;
	case TCIC_ID_DB86084:
	    printf("DB86084"); break;
	case TCIC_ID_DB86084A:
	    printf("DB86084A"); break;
	case TCIC_ID_DB86072:
	    printf("DB86072"); break;
	case TCIC_ID_DB86184:
	    printf("DB86184"); break;
	case TCIC_ID_DB86082B:
	    printf("DB86082B"); break;
	default:
	    printf("Unknown TCIC-2 ID 0x%02x", id);
	}
	printf(" found at %#6lx, %d sockets.\n", base, sock);
    }
    return 0;
    
} /* tcic_probe */

/*====================================================================*/

void main(int argc, char *argv[])
{
    int optch, errflg;
    extern char *optarg;
    int verbose = 0, module = 0;
    u_long tcic_base = TCIC_BASE;

    
    errflg = 0;
    while ((optch = getopt(argc, argv, "t:vxm")) != -1) {
	switch (optch) {
	case 't':
	    tcic_base = strtoul(optarg, NULL, 0); break;
	case 'v':
	    verbose = 1; break;
	case 'm':
	    module = 1; break;
	default:
	    errflg = 1; break;
	}
    }
    if (errflg || (optind < argc)) {
	fprintf(stderr, "usage: %s [-t tcic_base] [-v] [-m]\n", argv[0]);
	exit(EXIT_FAILURE);
    }

    if (verbose) printf("%s", version);

#ifdef CONFIG_PCI
    if (pci_probe(verbose, module) == 0)
	exit(EXIT_SUCCESS);
    else
#endif
    if (i365_probe(verbose, module) == 0)
	exit(EXIT_SUCCESS);
    else if (tcic_probe(verbose, module, tcic_base) == 0)
	exit(EXIT_SUCCESS);
    else
	exit(EXIT_FAILURE);
}
