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

    PCMCIA device control program

    Written by David Hinds, dhinds@allegro.stanford.edu

    cardctl.c 1.8 1995/02/07 23:16:38

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

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

#include "cs_types.h"
#include "cs.h"
#include "cistpl.h"
#include "ds.h"

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

static int lookup_dev(char *name)
{
    FILE *f;
    int n;
    char s[32], t[32];
    
    f = fopen("/proc/devices", "r");
    if (f == NULL)
	return -1;
    while (fgets(s, 32, f) != NULL) {
	if (sscanf(s, "%d %s", &n, t) == 2)
	    if (strcmp(name, t) == 0)
		break;
    }
    fclose(f);
    if (strcmp(name, t) == 0)
	return n;
    else
	return -1;
} /* lookup_dev */

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

static int open_dev(dev_t dev)
{
    char *fn;
    int fd;
    
    if ((fn = tmpnam(NULL)) == NULL)
	return -1;
    if (mknod(fn, (S_IFCHR|S_IREAD|S_IWRITE), dev) != 0)
	return -1;
    if ((fd = open(fn, O_RDONLY)) < 0) {
	unlink(fn);
	return -1;
    }
    if (unlink(fn) != 0) {
	close(fd);
	return -1;
    }
    return fd;
} /* open_dev */

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

static void print_status(status_t *status)
{
    printf("  ");
    if (status->CardState & CS_EVENT_CARD_DETECT)
	printf("card present");
    else {
	printf("no card\n");
	return;
    }
    if (status->CardState & CS_EVENT_PM_SUSPEND) {
	printf(", suspended\n");
	return;
    }
    if (status->CardState & CS_EVENT_READY_CHANGE)
	printf(", ready");
    if (status->CardState & CS_EVENT_WRITE_PROTECT)
	printf(", write protect");
    if (status->CardState & CS_EVENT_BATTERY_DEAD)
	printf(", battery dead ");
    if (status->CardState & CS_EVENT_BATTERY_LOW)
	printf(", battery low ");
    printf("\n");
} /* print_status */

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

static void print_config(config_t *config)
{
    printf("  Vcc = %.1f, Vpp1 = %.1f, Vpp2 = %.1f\n",
	   config->Vcc/10.0, config->Vpp1/10.0, config->Vpp2/10.0);
    printf("  Card type is %s\n", (config->IntType == INT_MEMORY)
	   ? "memory-only" : "memory and I/O");
    printf("  Config register base = %#06lx\n",
	   (long)config->ConfigBase);
    
    if (config->CardValues) {
	printf("  ");
	if (config->CardValues & CV_OPTION_VALUE)
	    printf("  Option = %#04x", config->Option);
	if (config->CardValues & CV_STATUS_VALUE)
	    printf("  Status = %#04x", config->Status);
	if (config->CardValues & CV_PIN_REPLACEMENT)
	    printf("  Pin = %#04x", config->Pin);
	if (config->CardValues & CV_COPY_VALUE)
	    printf("  Copy = %#04x", config->Copy);
	printf("\n");
    }

    if (config->AssignedIRQ != 0) {
	printf("  IRQ %ld is ", config->AssignedIRQ);
	switch (config->IRQAttributes & IRQ_TYPE) {
	case IRQ_TYPE_EXCLUSIVE:
	    printf("exclusive"); break;
	case IRQ_TYPE_TIME:
	    printf("time-multiplexed"); break;
	case IRQ_TYPE_DYNAMIC_SHARING:
	    printf("dynamic shared"); break;
	}
	if (config->IRQAttributes & IRQ_PULSE_ALLOCATED)
	    printf(", pulse mode");
	else
	    printf(", level mode");
	printf(", %s\n", (config->Attributes & CONF_ENABLE_IRQ) ?
	       "enabled" : "disabled");
    }

    if (config->Attributes & CONF_ENABLE_DMA)
	printf("  DMA mode is enabled\n");
    if (config->Attributes & CONF_ENABLE_SPKR)
	printf("  Speaker output is enabled\n");
    
    if (config->NumPorts1 > 0) {
	printf("  I/O window 1: %#06x to %#06x", config->BasePort1,
	       config->BasePort1 + config->NumPorts1 - 1);
	if (config->Attributes1 & IO_SHARED)
	    printf(", shared");
	if (config->Attributes1 & IO_FORCE_ALIAS_ACCESS)
	    printf(", force alias");
	switch (config->Attributes1 & IO_DATA_PATH_WIDTH) {
	case IO_DATA_PATH_WIDTH_8:
	    printf(", 8 bit\n"); break;
	case IO_DATA_PATH_WIDTH_16:
	    printf(", 16 bit\n"); break;
	case IO_DATA_PATH_WIDTH_AUTO:
	    printf(", auto sized\n"); break;
	}
    }
    if (config->NumPorts2 > 0) {
	printf("  I/O window 2: %#06x to %#06x", config->BasePort2,
	       config->BasePort2 + config->NumPorts2 - 1);
	if (config->Attributes2 & IO_SHARED)
	    printf(", shared");
	if (config->Attributes2 & IO_FORCE_ALIAS_ACCESS)
	    printf(", force alias");
	switch (config->Attributes2 & IO_DATA_PATH_WIDTH) {
	case IO_DATA_PATH_WIDTH_8:
	    printf(", 8 bit\n"); break;
	case IO_DATA_PATH_WIDTH_16:
	    printf(", 16 bit\n"); break;
	case IO_DATA_PATH_WIDTH_AUTO:
	    printf(", auto sized\n"); break;
	}
    }
} /* print_config */

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

typedef enum cmd_t {
    C_STATUS, C_CONFIG, C_SUSPEND, C_RESUME, C_RESET, C_EJECT, C_INSERT
} cmd_t;

static char *cmdname[] = {
    "status", "config", "suspend", "resume", "reset", "eject", "insert"
};

#define NCMD (sizeof(cmdname)/sizeof(char *))

static void do_cmd(int fd, int cmd)
{
    int ret;
    status_t status;
    config_t config;

    ret = 0;
    switch (cmd) {
	
    case C_STATUS:
	ret = ioctl(fd, DS_GET_STATUS, &status);
	if (ret == 0)
	    print_status(&status);
	break;
	
    case C_CONFIG:
	ret = ioctl(fd, DS_GET_CONFIGURATION_INFO, &config);
	if (ret == 0)
	    print_config(&config);
	break;

    case C_SUSPEND:
	ret = ioctl(fd, DS_SUSPEND_CARD);
	break;
	
    case C_RESUME:
	ret = ioctl(fd, DS_RESUME_CARD);
	break;

    case C_RESET:
	ret = ioctl(fd, DS_RESET_CARD);
	break;

    case C_EJECT:
	ret = ioctl(fd, DS_EJECT_CARD);
	break;
	
    case C_INSERT:
	ret = ioctl(fd, DS_INSERT_CARD);
	break;
    }
    if (ret != 0)
	perror("ioctl()");
}

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

void usage(char *name)
{
    int i;
    fprintf(stderr, "usage: %s command [socket #]\n", name);
    fprintf(stderr, "  commands:");
    for (i = 0; i < NCMD; i++)
	fprintf(stderr, " %s", cmdname[i]);
    fprintf(stderr, "\n");
    exit(EXIT_FAILURE);
}

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

#define MAX_SOCKS 8

void main(int argc, char *argv[])
{
    int major, cmd, fd, ns;

    major = lookup_dev("pcmcia");
    if (major < 0) {
	fprintf(stderr, "no pcmcia driver in /proc/devices\n");
	exit(EXIT_FAILURE);
    }

    if ((argc < 2) || (argc > 3))
	usage(argv[0]);

    for (cmd = 0; cmd < NCMD; cmd++)
	if (strcmp(argv[1], cmdname[cmd]) == 0) break;
    if (cmd == NCMD)
	usage(argv[0]);
	
    if (argc == 3) {
	ns = atoi(argv[2]);
	fd = open_dev((major<<8)+ns);
	if (fd < 0)
	    perror("open_dev()");
	else
	    do_cmd(fd, cmd);
    }
    else {
	for (ns = 0; ns < MAX_SOCKS; ns++) {
	    fd = open_dev((major<<8)+ns);
	    if (fd < 0) break;
	    if (cmd <= C_CONFIG)
		printf("Socket %d:\n", ns);
	    do_cmd(fd, cmd);
	}
    }
}
