/*
 *  (C) 2004-2005  Dominik Brodowski <linux@brodo.de>
 *
 * Partly based on cardctl.c from pcmcia-cs-3.2.7/cardmgr/, which states
 * in its header:
 *
 * The initial developer of the original code is David A. Hinds
 * <dahinds@users.sourceforge.net>.  Portions created by David A. Hinds
 *  are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 */

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <libintl.h>
#include <locale.h>

#include <sysfs/libsysfs.h>

#include <getopt.h>

#define MAX_SOCKET 8


static int pccardctl_echo_one(unsigned long socket_no, const char *in_file)
{
        int ret;
        char file[SYSFS_PATH_MAX];
        struct sysfs_attribute *attr;

	if (!file)
		return -EINVAL;

        snprintf(file, SYSFS_PATH_MAX, "/sys/class/pcmcia_socket/pcmcia_socket%lu/%s",
                 socket_no, in_file);

        attr = sysfs_open_attribute(file);
        if (!attr)
                return -ENODEV;

        ret = sysfs_write_attribute(attr, "42", 2);

        sysfs_close_attribute(attr);

        return (ret);
}

static int pccardctl_socket_exists(unsigned long socket_no)
{
	char file[SYSFS_PATH_MAX];

	snprintf(file, SYSFS_PATH_MAX,
		 "/sys/class/pcmcia_socket/pcmcia_socket%lu/card_insert",
		 socket_no);

	return (!(sysfs_path_is_file(file)));
}

static char * pccardctl_get_string(unsigned long socket_no, const char *in_file)
{
	int ret, len;
	char value[SYSFS_PATH_MAX];
	char file[SYSFS_PATH_MAX];
	char *result;

	snprintf(file, SYSFS_PATH_MAX, "/sys/bus/pcmcia/devices/%lu.0/%s",
		 socket_no, in_file);
	ret = sysfs_read_attribute_value(file, value, SYSFS_PATH_MAX);
	if (ret)
		return NULL;

	len = strlen(value);
	if (len < 2)
		return NULL;

	result = malloc(sizeof(char) * len);
	if (!result)
		return NULL;

        strncpy(result, value, len);
        result[len - 1] = '\0';

	return (result);
}

static int pccardctl_get_one(unsigned long socket_no, const char *in_file, unsigned int *result)
{
	int ret;
	char value[SYSFS_PATH_MAX];
	char file[SYSFS_PATH_MAX];

	snprintf(file, SYSFS_PATH_MAX, "/sys/bus/pcmcia/devices/%lu.0/%s",
		 socket_no, in_file);
	ret = sysfs_read_attribute_value(file, value, SYSFS_PATH_MAX);
	if (!ret)
		sscanf(value, "0x%X", result);

	return (ret);
}

static int pccardctl_ident(unsigned long socket_no)
{
	static char *fn[] = {
		"multifunction",
		"memory",
		"serial",
		"parallel",
		"fixed disk",
		"video",
		"network",
		"AIMS",
		"SCSI"
	};
	char *prod_id[4];
	int valid_prod_id = 0;
	int i;
	unsigned int manf_id, card_id;

	if (!pccardctl_socket_exists(socket_no))
		return -ENODEV;

	for (i=1; i<=4; i++) {
		char file[SYSFS_PATH_MAX];
		snprintf(file, SYSFS_PATH_MAX, "prod_id%u", i);

		prod_id[i-1] = pccardctl_get_string(socket_no, file);
		if (prod_id[i-1])
			valid_prod_id++;
	}

	if (valid_prod_id) {
		printf("  product info: ");
		for (i=0;i<4;i++) {
			printf("%s\"%s\"", (i>0) ? ", " : "",
			       prod_id[i] ? prod_id[i] : "" );
			if (prod_id[i])
				free(prod_id[i]);
		}
		printf("\n");
	} else
		printf("  no product info available\n");

	if (!pccardctl_get_one(socket_no, "manf_id", &manf_id))
		if (!pccardctl_get_one(socket_no, "card_id", &card_id))
			printf("  manfid: 0x%04x, 0x%04x\n", manf_id, card_id);

	if (!pccardctl_get_one(socket_no, "func_id", &manf_id))
		printf("  function: %d (%s)\n", manf_id, fn[manf_id]);


	return 0;
}

static int pccardctl_info(unsigned long socket_no)
{
	int i;
	unsigned int manf_id, card_id, func_id;
	char *prod_id;

	if (!pccardctl_socket_exists(socket_no))
		return -ENODEV;

	for (i=1; i<=4; i++) {
		char file[SYSFS_PATH_MAX];
		snprintf(file, SYSFS_PATH_MAX, "prod_id%u", i);

		prod_id = pccardctl_get_string(socket_no, file);

		printf("PRODID_%d=\"%s\"\n", i, (prod_id) ? prod_id : "");

		free(prod_id);
		prod_id = NULL;
	}

	printf("MANFID=%04x,%04x\n",
	       (!pccardctl_get_one(socket_no, "manf_id", &manf_id)) ? manf_id : 0,
	       (!pccardctl_get_one(socket_no, "card_id", &card_id)) ? card_id : 0);

	printf("FUNCID=%d\n",
	       (!pccardctl_get_one(socket_no, "func_id", &func_id)) ? func_id : 255);

	return 0;
}

static void print_header(void) {
	printf("pccardctl (C) 2004 Dominik Brodowski, (C) 1999 David A. Hinds\n");
	printf(gettext ("Report errors and bugs to <linux-pcmcia@lists.infradead.org>, please.\n"));
}

static void print_help(void) {
	/* TBD */
}

static void print_unknown_arg(void) {
	print_header();
	printf(gettext ("invalid or unknown argument\n"));
	print_help();
}

static struct option pccardctl_opts[] = {
	{ .name="version",	.has_arg=no_argument,		.flag=NULL,	.val='V'},
	{ .name="help",		.has_arg=no_argument,		.flag=NULL,	.val='h'},
//	{ .name="socket",	.has_arg=required_argument,	.flag=NULL,	.val='s'},
//	{ .name="socketdir",	.has_arg=required_argument,	.flag=NULL,	.val='d'},
};

enum {
	PCCARDCTL_INSERT,
	PCCARDCTL_EJECT,
	PCCARDCTL_SUSPEND,
	PCCARDCTL_RESUME,
	PCCARDCTL_RESET,
	PCCARDCTL_INFO,
	PCCARDCTL_STATUS,
	PCCARDCTL_CONFIG,
	PCCARDCTL_IDENT,
	NCMD
};

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


int main(int argc, char **argv) {
	extern char *optarg;
	extern int optind, opterr, optopt;
	int ret = 0;
	unsigned int cont = 1;
	unsigned long socket = 0;
	unsigned int socket_is_set = 0;
	char *s;
	unsigned int cmd;

	do {
		ret = getopt_long(argc, argv, "Vhc:f:s:", pccardctl_opts, NULL);
		switch (ret) {
		case -1:
			cont = 0;
			break;
		case 'V':
			print_header();
			return 0;
		case 'h':
			print_header();
			print_help();
			return 0;
		case 'c':
		case 's':
		case 'f':
			/* ignored */
			fprintf(stderr, "ignoring parameter '%c'\n", ret);
			break;
		default:
			print_unknown_arg();
			return -EINVAL;
		}
	} while (cont);

	if ((argc == optind) || (argc > (optind + 2))) {
		print_unknown_arg();
		return -EINVAL;
	}

	/* determine command */
	for (cmd = 0; cmd < NCMD; cmd++)
		if (strcmp(argv[optind], cmdname[cmd]) == 0)
			break;
	if (cmd == NCMD) {
		print_unknown_arg();
		return -EINVAL;
	}

	if (argc == optind+2) {
		socket_is_set = 1;
		socket = strtol(argv[optind+1], &s, 0);
		if ((*argv[optind+1] == '\0') || (*s != '\0') || (socket >= MAX_SOCKET)) {
			print_unknown_arg();
			return -EINVAL;
		}
	}

	for (cont = 0; cont < MAX_SOCKET; cont++) {
		if (socket_is_set && (socket != cont))
			continue;

		if (!socket_is_set && !pccardctl_socket_exists(cont))
			continue;

		if (!socket_is_set && (cmd > PCCARDCTL_INFO))
			printf("Socket %d:\n", cont);

		ret = 0;
		switch (cmd) {
		case PCCARDCTL_INSERT:
			ret = pccardctl_echo_one(cont, "card_insert");
			break;
		case PCCARDCTL_EJECT:
			ret = pccardctl_echo_one(cont, "card_eject");
			break;
		case PCCARDCTL_INFO:
			ret = pccardctl_info(cont);
			break;
		case PCCARDCTL_IDENT:
			ret = pccardctl_ident(cont);
			break;
		default:
			fprintf(stderr, "command '%s' not yet handled by pccardctl\n", cmdname[cmd]);
			return -EAGAIN;
		}

		if (ret && socket_is_set)
			return (ret);
	}

	return 0;
}
