/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/socket.h>
#include <unistd.h>

#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"

#include "csr.h"

#define CSR_TRANSPORT_UNKNOWN	0
#define CSR_TRANSPORT_HCI	1
#define CSR_TRANSPORT_USB	2
#define CSR_TRANSPORT_BCSP	3
#define CSR_TRANSPORT_H4	4
#define CSR_TRANSPORT_3WIRE	5

#define CSR_STORES_PSI		(0x0001)
#define CSR_STORES_PSF		(0x0002)
#define CSR_STORES_PSROM	(0x0004)
#define CSR_STORES_PSRAM	(0x0008)
#define CSR_STORES_DEFAULT	(CSR_STORES_PSI | CSR_STORES_PSF)

#define CSR_TYPE_NULL		0
#define CSR_TYPE_COMPLEX	1
#define CSR_TYPE_UINT8		2
#define CSR_TYPE_UINT16		3
#define CSR_TYPE_UINT32		4

#define CSR_TYPE_ARRAY		CSR_TYPE_COMPLEX
#define CSR_TYPE_BDADDR		CSR_TYPE_COMPLEX

static inline int transport_open(int transport, char *device, speed_t bcsp_rate)
{
	switch (transport) {
	case CSR_TRANSPORT_HCI:
		return csr_open_hci(device);
	case CSR_TRANSPORT_USB:
		return csr_open_usb(device);
	case CSR_TRANSPORT_BCSP:
		return csr_open_bcsp(device, bcsp_rate);
	case CSR_TRANSPORT_H4:
		return csr_open_h4(device);
	case CSR_TRANSPORT_3WIRE:
		return csr_open_3wire(device);
	default:
		fprintf(stderr, "Unsupported transport\n");
		return -1;
	}
}

static inline int transport_read(int transport, uint16_t varid, uint8_t *value, uint16_t length)
{
	switch (transport) {
	case CSR_TRANSPORT_HCI:
		return csr_read_hci(varid, value, length);
	case CSR_TRANSPORT_USB:
		return csr_read_usb(varid, value, length);
	case CSR_TRANSPORT_BCSP:
		return csr_read_bcsp(varid, value, length);
	case CSR_TRANSPORT_H4:
		return csr_read_h4(varid, value, length);
	case CSR_TRANSPORT_3WIRE:
		return csr_read_3wire(varid, value, length);
	default:
		errno = EOPNOTSUPP;
		return -1;
	}
}

static inline int transport_write(int transport, uint16_t varid, uint8_t *value, uint16_t length)
{
	switch (transport) {
	case CSR_TRANSPORT_HCI:
		return csr_write_hci(varid, value, length);
	case CSR_TRANSPORT_USB:
		return csr_write_usb(varid, value, length);
	case CSR_TRANSPORT_BCSP:
		return csr_write_bcsp(varid, value, length);
	case CSR_TRANSPORT_H4:
		return csr_write_h4(varid, value, length);
	case CSR_TRANSPORT_3WIRE:
		return csr_write_3wire(varid, value, length);
	default:
		errno = EOPNOTSUPP;
		return -1;
	}
}

static inline void transport_close(int transport)
{
	switch (transport) {
	case CSR_TRANSPORT_HCI:
		csr_close_hci();
		break;
	case CSR_TRANSPORT_USB:
		csr_close_usb();
		break;
	case CSR_TRANSPORT_BCSP:
		csr_close_bcsp();
		break;
	case CSR_TRANSPORT_H4:
		csr_close_h4();
		break;
	case CSR_TRANSPORT_3WIRE:
		csr_close_3wire();
		break;
	}
}

static struct {
	uint16_t pskey;
	int type;
	int size;
	char *str;
} storage[] = {
	{ CSR_PSKEY_BDADDR,                   CSR_TYPE_BDADDR,  8,  "bdaddr"   },
	{ CSR_PSKEY_COUNTRYCODE,              CSR_TYPE_UINT16,  0,  "country"  },
	{ CSR_PSKEY_CLASSOFDEVICE,            CSR_TYPE_UINT32,  0,  "devclass" },
	{ CSR_PSKEY_ENC_KEY_LMIN,             CSR_TYPE_UINT16,  0,  "keymin"   },
	{ CSR_PSKEY_ENC_KEY_LMAX,             CSR_TYPE_UINT16,  0,  "keymax"   },
	{ CSR_PSKEY_LOCAL_SUPPORTED_FEATURES, CSR_TYPE_ARRAY,   8,  "features" },
	{ CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS, CSR_TYPE_ARRAY,   18, "commands" },
	{ CSR_PSKEY_HCI_LMP_LOCAL_VERSION,    CSR_TYPE_UINT16,  0,  "version"  },
	{ CSR_PSKEY_LMP_REMOTE_VERSION,       CSR_TYPE_UINT8,   0,  "remver"   },
	{ CSR_PSKEY_HOSTIO_USE_HCI_EXTN,      CSR_TYPE_UINT16,  0,  "hciextn"  },
	{ CSR_PSKEY_HOSTIO_MAP_SCO_PCM,       CSR_TYPE_UINT16,  0,  "mapsco"   },
	{ CSR_PSKEY_UART_BAUDRATE,            CSR_TYPE_UINT16,  0,  "baudrate" },
	{ CSR_PSKEY_HOST_INTERFACE,           CSR_TYPE_UINT16,  0,  "hostintf" },
	{ CSR_PSKEY_ANA_FREQ,                 CSR_TYPE_UINT16,  0,  "anafreq"  },
	{ CSR_PSKEY_ANA_FTRIM,                CSR_TYPE_UINT16,  0,  "anaftrim" },
	{ CSR_PSKEY_USB_VENDOR_ID,            CSR_TYPE_UINT16,  0,  "usbvid"   },
	{ CSR_PSKEY_USB_PRODUCT_ID,           CSR_TYPE_UINT16,  0,  "usbpid"   },
	{ CSR_PSKEY_USB_DFU_PRODUCT_ID,       CSR_TYPE_UINT16,  0,  "dfupid"   },
	{ CSR_PSKEY_INITIAL_BOOTMODE,         CSR_TYPE_UINT16,  0,  "bootmode" },
	{ 0x0000 },
};

static char *storestostr(uint16_t stores)
{
	switch (stores) {
	case 0x0000:
		return "Default";
	case 0x0001:
		return "psi";
	case 0x0002:
		return "psf";
	case 0x0004:
		return "psrom";
	case 0x0008:
		return "psram";
	default:
		return "Unknown";
	}
}

static char *memorytostr(uint16_t type)
{
	switch (type) {
	case 0x0000:
		return "Flash memory";
	case 0x0001:
		return "EEPROM";
	case 0x0002:
		return "RAM (transient)";
	case 0x0003:
		return "ROM (or \"read-only\" flash memory)";
	default:
		return "Unknown";
	}
}

#define OPT_RANGE(min, max) \
		if (argc < (min)) { errno = EINVAL; return -1; } \
		if (argc > (max)) { errno = E2BIG; return -1; }

static struct option help_options[] = {
	{ "help",	0, 0, 'h' },
	{ 0, 0, 0, 0 }
};

static int opt_help(int argc, char *argv[], int *help)
{
	int opt;

	while ((opt=getopt_long(argc, argv, "+h", help_options, NULL)) != EOF) {
		switch (opt) {
		case 'h':
			if (help)
				*help = 1;
			break;
		}
	}

	return optind;
}

#define OPT_HELP(range, help) \
		opt_help(argc, argv, (help)); \
		argc -= optind; argv += optind; optind = 0; \
		OPT_RANGE((range), (range))

static int cmd_builddef(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t def = 0x0000, nextdef = 0x0000;
	int err = 0;

	OPT_HELP(0, NULL);

	printf("Build definitions:\n");

	while (1) {
		memset(array, 0, sizeof(array));
		array[0] = def & 0xff;
		array[1] = def >> 8;

		err = transport_read(transport, CSR_VARID_GET_NEXT_BUILDDEF, array, 8);
		if (err < 0)
			break;

		nextdef = array[2] | (array[3] << 8);

		if (nextdef == 0x0000)
			break;

		def = nextdef;

		printf("0x%04x - %s\n", def, csr_builddeftostr(def));
	}

	return err;
}

static int cmd_keylen(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t handle, keylen;
	int err;

	OPT_HELP(1, NULL);

	handle = atoi(argv[0]);

	memset(array, 0, sizeof(array));
	array[0] = handle & 0xff;
	array[1] = handle >> 8;

	err = transport_read(transport, CSR_VARID_CRYPT_KEY_LENGTH, array, 8);
	if (err < 0)
		return -1;

	keylen = array[2] | (array[3] << 8);

	printf("Crypt key length: %d bit\n", keylen * 8);

	return 0;
}

static int cmd_clock(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint32_t clock;
	int err;

	OPT_HELP(0, NULL);

	memset(array, 0, sizeof(array));

	err = transport_read(transport, CSR_VARID_BT_CLOCK, array, 8);
	if (err < 0)
		return -1;

	clock = array[2] | (array[3] << 8) | (array[0] << 16) | (array[1] << 24);

	printf("Bluetooth clock: 0x%04x (%d)\n", clock, clock);

	return 0;
}

static int cmd_rand(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t rand;
	int err;

	OPT_HELP(0, NULL);

	memset(array, 0, sizeof(array));

	err = transport_read(transport, CSR_VARID_RAND, array, 8);
	if (err < 0)
		return -1;

	rand = array[0] | (array[1] << 8);

	printf("Random number: 0x%02x (%d)\n", rand, rand);

	return 0;
}

static int cmd_chiprev(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t rev;
	char *str;
	int err;

	OPT_HELP(0, NULL);

	memset(array, 0, sizeof(array));

	err = transport_read(transport, CSR_VARID_CHIPREV, array, 8);
	if (err < 0)
		return -1;

	rev = array[0] | (array[1] << 8);

	switch (rev) {
	case 0x64:
		str = "BC1 ES";
		break;
	case 0x65:
		str = "BC1";
		break;
	case 0x89:
		str = "BC2-External A";
		break;
	case 0x8a:
		str = "BC2-External B";
		break;
	case 0x28:
		str = "BC2-ROM";
		break;
	case 0x43:
		str = "BC3-Multimedia";
		break;
	case 0x15:
		str = "BC3-ROM";
		break;
	case 0xe2:
		str = "BC3-Flash";
		break;
	case 0x26:
		str = "BC4-External";
		break;
	case 0x30:
		str = "BC4-ROM";
		break;
	default:
		str = "NA";
		break;
	}

	printf("Chip revision: 0x%04x (%s)\n", rev, str);

	return 0;
}

static int cmd_buildname(int transport, int argc, char *argv[])
{
	uint8_t array[131];
	char name[64];
	unsigned int i;
	int err;

	OPT_HELP(0, NULL);

	memset(array, 0, sizeof(array));

	err = transport_read(transport, CSR_VARID_READ_BUILD_NAME, array, 128);
	if (err < 0)
		return -1;

	for (i = 0; i < sizeof(name); i++)
		name[i] = array[(i * 2) + 4];

	printf("Build name: %s\n", name);

	return 0;
}

static int cmd_panicarg(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t error;
	int err;

	OPT_HELP(0, NULL);

	memset(array, 0, sizeof(array));

	err = transport_read(transport, CSR_VARID_PANIC_ARG, array, 8);
	if (err < 0)
		return -1;

	error = array[0] | (array[1] << 8);

	printf("Panic code: 0x%02x (%s)\n", error,
					error < 0x100 ? "valid" : "invalid");

	return 0;
}

static int cmd_faultarg(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t error;
	int err;

	OPT_HELP(0, NULL);

	memset(array, 0, sizeof(array));

	err = transport_read(transport, CSR_VARID_FAULT_ARG, array, 8);
	if (err < 0)
		return -1;

	error = array[0] | (array[1] << 8);

	printf("Fault code: 0x%02x (%s)\n", error,
					error < 0x100 ? "valid" : "invalid");

	return 0;
}

static int cmd_coldreset(int transport, int argc, char *argv[])
{
	return transport_write(transport, CSR_VARID_COLD_RESET, NULL, 0);
}

static int cmd_warmreset(int transport, int argc, char *argv[])
{
	return transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);
}

static int cmd_disabletx(int transport, int argc, char *argv[])
{
	return transport_write(transport, CSR_VARID_DISABLE_TX, NULL, 0);
}

static int cmd_enabletx(int transport, int argc, char *argv[])
{
	return transport_write(transport, CSR_VARID_ENABLE_TX, NULL, 0);
}

static int cmd_singlechan(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t channel;

	OPT_HELP(1, NULL);

	channel = atoi(argv[0]);

	if (channel > 2401 && channel < 2481)
		channel -= 2402;

	if (channel > 78) {
		errno = EINVAL;
		return -1;
	}

	memset(array, 0, sizeof(array));
	array[0] = channel & 0xff;
	array[1] = channel >> 8;

	return transport_write(transport, CSR_VARID_SINGLE_CHAN, array, 8);
}

static int cmd_hoppingon(int transport, int argc, char *argv[])
{
	return transport_write(transport, CSR_VARID_HOPPING_ON, NULL, 0);
}

static int cmd_rttxdata1(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t freq, level;

	OPT_HELP(2, NULL);

	freq = atoi(argv[0]);

	if (!strncasecmp(argv[1], "0x", 2))
		level = strtol(argv[1], NULL, 16);
	else
		level = atoi(argv[1]);

	memset(array, 0, sizeof(array));
	array[0] = 0x04;
	array[1] = 0x00;
	array[2] = freq & 0xff;
	array[3] = freq >> 8;
	array[4] = level & 0xff;
	array[5] = level >> 8;

	return transport_write(transport, CSR_VARID_RADIOTEST, array, 8);
}

static int cmd_radiotest(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t freq, level, test;

	OPT_HELP(3, NULL);

	freq = atoi(argv[0]);

	if (!strncasecmp(argv[1], "0x", 2))
		level = strtol(argv[1], NULL, 16);
	else
		level = atoi(argv[1]);

	test = atoi(argv[2]);

	memset(array, 0, sizeof(array));
	array[0] = test & 0xff;
	array[1] = test >> 8;
	array[2] = freq & 0xff;
	array[3] = freq >> 8;
	array[4] = level & 0xff;
	array[5] = level >> 8;

	return transport_write(transport, CSR_VARID_RADIOTEST, array, 8);
}

static int cmd_memtypes(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t type, stores[4] = { 0x0001, 0x0002, 0x0004, 0x0008 };
	int i, err;

	OPT_HELP(0, NULL);

	for (i = 0; i < 4; i++) {
		memset(array, 0, sizeof(array));
		array[0] = stores[i] & 0xff;
		array[1] = stores[i] >> 8;

		err = transport_read(transport, CSR_VARID_PS_MEMORY_TYPE, array, 8);
		if (err < 0)
			continue;

		type = array[2] + (array[3] << 8);

		printf("%s (0x%04x) = %s (%d)\n", storestostr(stores[i]),
					stores[i], memorytostr(type), type);
	}

	return 0;
}

static struct option pskey_options[] = {
	{ "stores",	1, 0, 's' },
	{ "reset",	0, 0, 'r' },
	{ "help",	0, 0, 'h' },
	{ 0, 0, 0, 0 }
};

static int opt_pskey(int argc, char *argv[], uint16_t *stores, int *reset, int *help)
{
	int opt;

	while ((opt=getopt_long(argc, argv, "+s:rh", pskey_options, NULL)) != EOF) {
		switch (opt) {
		case 's':
			if (!stores)
				break;
			if (!strcasecmp(optarg, "default"))
				*stores = 0x0000;
			else if (!strcasecmp(optarg, "implementation"))
				*stores = 0x0001;
			else if (!strcasecmp(optarg, "factory"))
				*stores = 0x0002;
			else if (!strcasecmp(optarg, "rom"))
				*stores = 0x0004;
			else if (!strcasecmp(optarg, "ram"))
				*stores = 0x0008;
			else if (!strcasecmp(optarg, "psi"))
				*stores = 0x0001;
			else if (!strcasecmp(optarg, "psf"))
				*stores = 0x0002;
			else if (!strcasecmp(optarg, "psrom"))
				*stores = 0x0004;
			else if (!strcasecmp(optarg, "psram"))
				*stores = 0x0008;
			else if (!strncasecmp(optarg, "0x", 2))
				*stores = strtol(optarg, NULL, 16);
			else
				*stores = atoi(optarg);
			break;

		case 'r':
			if (reset)
				*reset = 1;
			break;

		case 'h':
			if (help)
				*help = 1;
			break;
		}
	}

	return optind;
}

#define OPT_PSKEY(min, max, stores, reset, help) \
		opt_pskey(argc, argv, (stores), (reset), (help)); \
		argc -= optind; argv += optind; optind = 0; \
		OPT_RANGE((min), (max))

static int cmd_psget(int transport, int argc, char *argv[])
{
	uint8_t array[128];
	uint16_t pskey, length, value, stores = CSR_STORES_DEFAULT;
	uint32_t val32;
	int i, err, reset = 0;

	memset(array, 0, sizeof(array));

	OPT_PSKEY(1, 1, &stores, &reset, NULL);

	if (strncasecmp(argv[0], "0x", 2)) {
		pskey = atoi(argv[0]);

		for (i = 0; storage[i].pskey; i++) {
			if (strcasecmp(storage[i].str, argv[0]))
				continue;

			pskey = storage[i].pskey;
			break;
		}
	} else
		pskey = strtol(argv[0] + 2, NULL, 16);

	memset(array, 0, sizeof(array));
	array[0] = pskey & 0xff;
	array[1] = pskey >> 8;
	array[2] = stores & 0xff;
	array[3] = stores >> 8;

	err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
	if (err < 0)
		return err;

	length = array[2] + (array[3] << 8);
	if (length + 6 > (int) sizeof(array) / 2)
		return -EIO;

	memset(array, 0, sizeof(array));
	array[0] = pskey & 0xff;
	array[1] = pskey >> 8;
	array[2] = length & 0xff;
	array[3] = length >> 8;
	array[4] = stores & 0xff;
	array[5] = stores >> 8;

	err = transport_read(transport, CSR_VARID_PS, array, (length + 3) * 2);
	if (err < 0)
		return err;

	switch (length) {
	case 1:
		value = array[6] | (array[7] << 8);
		printf("%s: 0x%04x (%d)\n", csr_pskeytostr(pskey), value, value);
		break;

	case 2:
		val32 = array[8] | (array[9] << 8) | (array[6] << 16) | (array[7] << 24);
		printf("%s: 0x%08x (%d)\n", csr_pskeytostr(pskey), val32, val32);
		break;

	default:
		printf("%s:", csr_pskeytostr(pskey));
		for (i = 0; i < length; i++)
			printf(" 0x%02x%02x", array[(i * 2) + 6], array[(i * 2) + 7]);
		printf("\n");
		break;
	}

	if (reset)
		transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);

	return err;
}

static int cmd_psset(int transport, int argc, char *argv[])
{
	uint8_t array[128];
	uint16_t pskey, length, value, stores = CSR_STORES_PSRAM;
	uint32_t val32;
	int i, err, reset = 0;

	memset(array, 0, sizeof(array));

	OPT_PSKEY(2, 81, &stores, &reset, NULL);

	if (strncasecmp(argv[0], "0x", 2)) {
		pskey = atoi(argv[0]);

		for (i = 0; storage[i].pskey; i++) {
			if (strcasecmp(storage[i].str, argv[0]))
				continue;

			pskey = storage[i].pskey;
			break;
		}
	} else
		pskey = strtol(argv[0] + 2, NULL, 16);

	memset(array, 0, sizeof(array));
	array[0] = pskey & 0xff;
	array[1] = pskey >> 8;
	array[2] = stores & 0xff;
	array[3] = stores >> 8;

	err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
	if (err < 0)
		return err;

	length = array[2] + (array[3] << 8);
	if (length + 6 > (int) sizeof(array) / 2)
		return -EIO;

	memset(array, 0, sizeof(array));
	array[0] = pskey & 0xff;
	array[1] = pskey >> 8;
	array[2] = length & 0xff;
	array[3] = length >> 8;
	array[4] = stores & 0xff;
	array[5] = stores >> 8;

	argc--;
	argv++;

	switch (length) {
	case 1:
		if (argc != 1) {
			errno = E2BIG;
			return -1;
		}

		if (!strncasecmp(argv[0], "0x", 2))
			value = strtol(argv[0] + 2, NULL, 16);
		else
			value = atoi(argv[0]);

		array[6] = value & 0xff;
		array[7] = value >> 8;
		break;

	case 2:
		if (argc != 1) {
			errno = E2BIG;
			return -1;
		}

		if (!strncasecmp(argv[0], "0x", 2))
			val32 = strtol(argv[0] + 2, NULL, 16);
		else
			val32 = atoi(argv[0]);

		array[6] = (val32 & 0xff0000) >> 16;
		array[7] = val32 >> 24;
		array[8] = val32 & 0xff;
		array[9] = (val32 & 0xff00) >> 8;
		break;

	default:
		if (argc != length * 2) {
			errno = EINVAL;
			return -1;
		}

		for (i = 0; i < length * 2; i++)
			if (!strncasecmp(argv[0], "0x", 2))
				array[i + 6] = strtol(argv[i] + 2, NULL, 16);
			else
				array[i + 6] = atoi(argv[i]);
		break;
	}

	err = transport_write(transport, CSR_VARID_PS, array, (length + 3) * 2);
	if (err < 0)
		return err;

	if (reset)
		transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);

	return err;
}

static int cmd_psclr(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t pskey, stores = CSR_STORES_PSRAM;
	int i, err, reset = 0;

	OPT_PSKEY(1, 1, &stores, &reset, NULL);

	if (strncasecmp(argv[0], "0x", 2)) {
		pskey = atoi(argv[0]);

		for (i = 0; storage[i].pskey; i++) {
			if (strcasecmp(storage[i].str, argv[0]))
				continue;

			pskey = storage[i].pskey;
			break;
		}
	} else
		pskey = strtol(argv[0] + 2, NULL, 16);

	memset(array, 0, sizeof(array));
	array[0] = pskey & 0xff;
	array[1] = pskey >> 8;
	array[2] = stores & 0xff;
	array[3] = stores >> 8;

	err = transport_write(transport, CSR_VARID_PS_CLR_STORES, array, 8);
	if (err < 0)
		return err;

	if (reset)
		transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);

	return err;
}

static int cmd_pslist(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t pskey = 0x0000, length, stores = CSR_STORES_DEFAULT;
	int err, reset = 0;

	OPT_PSKEY(0, 0, &stores, &reset, NULL);

	while (1) {
		memset(array, 0, sizeof(array));
		array[0] = pskey & 0xff;
		array[1] = pskey >> 8;
		array[2] = stores & 0xff;
		array[3] = stores >> 8;

		err = transport_read(transport, CSR_VARID_PS_NEXT, array, 8);
		if (err < 0)
			break;

		pskey = array[4] + (array[5] << 8);
		if (pskey == 0x0000)
			break;

		memset(array, 0, sizeof(array));
		array[0] = pskey & 0xff;
		array[1] = pskey >> 8;
		array[2] = stores & 0xff;
		array[3] = stores >> 8;

		err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
		if (err < 0)
			continue;

		length = array[2] + (array[3] << 8);

		printf("0x%04x - %s (%d bytes)\n", pskey,
					csr_pskeytostr(pskey), length * 2);
	}

	if (reset)
		transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);

	return 0;
}

static int cmd_psread(int transport, int argc, char *argv[])
{
	uint8_t array[256];
	uint16_t pskey = 0x0000, length, stores = CSR_STORES_DEFAULT;
	char *str, val[7];
	int i, err, reset = 0;

	OPT_PSKEY(0, 0, &stores, &reset, NULL);

	while (1) {
		memset(array, 0, sizeof(array));
		array[0] = pskey & 0xff;
		array[1] = pskey >> 8;
		array[2] = stores & 0xff;
		array[3] = stores >> 8;

		err = transport_read(transport, CSR_VARID_PS_NEXT, array, 8);
		if (err < 0)
			break;

		pskey = array[4] + (array[5] << 8);
		if (pskey == 0x0000)
			break;

		memset(array, 0, sizeof(array));
		array[0] = pskey & 0xff;
		array[1] = pskey >> 8;
		array[2] = stores & 0xff;
		array[3] = stores >> 8;

		err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8);
		if (err < 0)
			continue;

		length = array[2] + (array[3] << 8);
		if (length + 6 > (int) sizeof(array) / 2)
			continue;

		memset(array, 0, sizeof(array));
		array[0] = pskey & 0xff;
		array[1] = pskey >> 8;
		array[2] = length & 0xff;
		array[3] = length >> 8;
		array[4] = stores & 0xff;
		array[5] = stores >> 8;

		err = transport_read(transport, CSR_VARID_PS, array, (length + 3) * 2);
		if (err < 0)
			continue;

		str = csr_pskeytoval(pskey);
		if (!strcasecmp(str, "UNKNOWN")) {
			sprintf(val, "0x%04x", pskey);
			str = NULL;
		}

		printf("// %s%s\n&%04x =", str ? "PSKEY_" : "",
						str ? str : val, pskey);
		for (i = 0; i < length; i++)
			printf(" %02x%02x", array[(i * 2) + 7], array[(i * 2) + 6]);
		printf("\n");
	}

	if (reset)
		transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);

	return 0;
}

static int cmd_psload(int transport, int argc, char *argv[])
{
	uint8_t array[256];
	uint16_t pskey, length, size, stores = CSR_STORES_PSRAM;
	char *str, val[7];
	int err, reset = 0;

	OPT_PSKEY(1, 1, &stores, &reset, NULL);

	psr_read(argv[0]);

	memset(array, 0, sizeof(array));
	size = sizeof(array) - 6;

	while (psr_get(&pskey, array + 6, &size) == 0) {
		str = csr_pskeytoval(pskey);
		if (!strcasecmp(str, "UNKNOWN")) {
			sprintf(val, "0x%04x", pskey);
			str = NULL;
		}

		printf("Loading %s%s ... ", str ? "PSKEY_" : "",
							str ? str : val);
		fflush(stdout);

		length = size / 2;

		array[0] = pskey & 0xff;
		array[1] = pskey >> 8;
		array[2] = length & 0xff;
		array[3] = length >> 8;
		array[4] = stores & 0xff;
		array[5] = stores >> 8;

		err = transport_write(transport, CSR_VARID_PS, array, size + 6);

		printf("%s\n", err < 0 ? "failed" : "done");

		memset(array, 0, sizeof(array));
		size = sizeof(array) - 6;
	}

	if (reset)
		transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0);

	return 0;
}

static int cmd_pscheck(int transport, int argc, char *argv[])
{
	uint8_t array[256];
	uint16_t pskey, size;
	int i;

	OPT_HELP(1, NULL);

	psr_read(argv[0]);

	while (psr_get(&pskey, array, &size) == 0) {
		printf("0x%04x =", pskey);
		for (i = 0; i < size; i++)
			printf(" 0x%02x", array[i]);
		printf("\n");
	}

	return 0;
}

static int cmd_adc(int transport, int argc, char *argv[])
{
	uint8_t array[8];
	uint16_t mux, value;
	int err;

	OPT_HELP(1, NULL);

	if (!strncasecmp(argv[0], "0x", 2))
		mux = strtol(argv[0], NULL, 16);
	else
		mux = atoi(argv[0]);

	/* Request an ADC read from a particular mux'ed input */
	memset(array, 0, sizeof(array));
	array[0] = mux & 0xff;
	array[1] = mux >> 8;

	err = transport_write(transport, CSR_VARID_ADC, array, 2);
	if (err < 0) {
		errno = -err;
		return -1;
	}

	/* have to wait a short while, then read result */
	usleep(50000);
	err = transport_read(transport, CSR_VARID_ADC_RES, array, 8);
	if (err < 0) {
		errno = -err;
		return -1;
	}

	mux = array[0] | (array[1] << 8);
	value = array[4] | (array[5] << 8);

	printf("ADC value from Mux 0x%02x : 0x%04x (%s)\n", mux, value,
					array[2] == 1 ? "valid" : "invalid");

	return 0;
}

static struct {
	char *str;
	int (*func)(int transport, int argc, char *argv[]);
	char *arg;
	char *doc;
} commands[] = {
	{ "builddef",  cmd_builddef,  "",                    "Get build definitions"          },
	{ "keylen",    cmd_keylen,    "<handle>",            "Get current crypt key length"   },
	{ "clock",     cmd_clock,     "",                    "Get local Bluetooth clock"      },
	{ "rand",      cmd_rand,      "",                    "Get random number"              },
	{ "chiprev",   cmd_chiprev,   "",                    "Get chip revision"              },
	{ "buildname", cmd_buildname, "",                    "Get the full build name"        },
	{ "panicarg",  cmd_panicarg,  "",                    "Get panic code argument"        },
	{ "faultarg",  cmd_faultarg,  "",                    "Get fault code argument"        },
	{ "coldreset", cmd_coldreset, "",                    "Perform cold reset"             },
	{ "warmreset", cmd_warmreset, "",                    "Perform warm reset"             },
	{ "disabletx", cmd_disabletx, "",                    "Disable TX on the device"       },
	{ "enabletx",  cmd_enabletx,  "",                    "Enable TX on the device"        },
	{ "singlechan",cmd_singlechan,"<channel>",           "Lock radio on specific channel" },
	{ "hoppingon", cmd_hoppingon, "",                    "Revert to channel hopping"      },
	{ "rttxdata1", cmd_rttxdata1, "<freq> <level>",      "TXData1 radio test"             },
	{ "radiotest", cmd_radiotest, "<freq> <level> <id>", "Run radio tests"                },
	{ "memtypes",  cmd_memtypes,  NULL,                  "Get memory types"               },
	{ "psget",     cmd_psget,     "<key>",               "Get value for PS key"           },
	{ "psset",     cmd_psset,     "<key> <value>",       "Set value for PS key"           },
	{ "psclr",     cmd_psclr,     "<key>",               "Clear value for PS key"         },
	{ "pslist",    cmd_pslist,    NULL,                  "List all PS keys"               },
	{ "psread",    cmd_psread,    NULL,                  "Read all PS keys"               },
	{ "psload",    cmd_psload,    "<file>",              "Load all PS keys from PSR file" },
	{ "pscheck",   cmd_pscheck,   "<file>",              "Check PSR file"                 },
	{ "adc",       cmd_adc,       "<mux>",               "Read ADC value of <mux> input"  },
	{ NULL }
};

static void usage(void)
{
	int i, pos = 0;

	printf("bccmd - Utility for the CSR BCCMD interface\n\n");
	printf("Usage:\n"
		"\tbccmd [options] <command>\n\n");

	printf("Options:\n"
		"\t-t <transport>     Select the transport\n"
		"\t-d <device>        Select the device\n"
		"\t-b <bcsp rate>     Select the bcsp transfer rate\n"
		"\t-h, --help         Display help\n"
		"\n");

	printf("Transports:\n"
		"\tHCI USB BCSP H4 3WIRE\n\n");

	printf("Commands:\n");
	for (i = 0; commands[i].str; i++)
		printf("\t%-10s %-20s\t%s\n", commands[i].str,
		commands[i].arg ? commands[i].arg : " ",
		commands[i].doc);
	printf("\n");

	printf("Keys:\n\t");
	for (i = 0; storage[i].pskey; i++) {
		printf("%s ", storage[i].str);
		pos += strlen(storage[i].str) + 1;
		if (pos > 60) {
			printf("\n\t");
			pos = 0;
		}
	}
	printf("\n");
}

static struct option main_options[] = {
	{ "transport",	1, 0, 't' },
	{ "device",	1, 0, 'd' },
	{ "bcsprate", 1, 0, 'b'},
	{ "help",	0, 0, 'h' },
	{ 0, 0, 0, 0 }
};

int main(int argc, char *argv[])
{
	char *device = NULL;
	int i, err, opt, transport = CSR_TRANSPORT_HCI;
	speed_t bcsp_rate = B38400;

	while ((opt=getopt_long(argc, argv, "+t:d:i:b:h", main_options, NULL)) != EOF) {
		switch (opt) {
		case 't':
			if (!strcasecmp(optarg, "hci"))
				transport = CSR_TRANSPORT_HCI;
			else if (!strcasecmp(optarg, "usb"))
				transport = CSR_TRANSPORT_USB;
			else if (!strcasecmp(optarg, "bcsp"))
				transport = CSR_TRANSPORT_BCSP;
			else if (!strcasecmp(optarg, "h4"))
				transport = CSR_TRANSPORT_H4;
			else if (!strcasecmp(optarg, "h5"))
				transport = CSR_TRANSPORT_3WIRE;
			else if (!strcasecmp(optarg, "3wire"))
				transport = CSR_TRANSPORT_3WIRE;
			else if (!strcasecmp(optarg, "twutl"))
				transport = CSR_TRANSPORT_3WIRE;
			else
				transport = CSR_TRANSPORT_UNKNOWN;
			break;

		case 'd':
		case 'i':
			device = strdup(optarg);
			break;
		case 'b':
			switch (atoi(optarg)) {
			case 9600: bcsp_rate = B9600; break;
			case 19200: bcsp_rate = B19200; break;
			case 38400: bcsp_rate = B38400; break;
			case 57600: bcsp_rate = B57600; break;
			case 115200: bcsp_rate = B115200; break;
			case 230400: bcsp_rate = B230400; break;
			case 460800: bcsp_rate = B460800; break;
			case 500000: bcsp_rate = B500000; break;
			case 576000: bcsp_rate = B576000; break;
			case 921600: bcsp_rate = B921600; break;
			case 1000000: bcsp_rate = B1000000; break;
			case 1152000: bcsp_rate = B1152000; break;
			case 1500000: bcsp_rate = B1500000; break;
			case 2000000: bcsp_rate = B2000000; break;
#ifdef B2500000
			case 2500000: bcsp_rate = B2500000; break;
#endif
#ifdef B3000000
			case 3000000: bcsp_rate = B3000000; break;
#endif
#ifdef B3500000
			case 3500000: bcsp_rate = B3500000; break;
#endif
#ifdef B4000000
			case 4000000: bcsp_rate = B4000000; break;
#endif
			default:
				printf("Unknown BCSP baud rate specified, defaulting to 38400bps\n");
				bcsp_rate = B38400;
			}
			break;
		case 'h':
		default:
			usage();
			exit(0);
		}
	}

	argc -= optind;
	argv += optind;
	optind = 0;

	if (argc < 1) {
		usage();
		exit(1);
	}

	if (transport_open(transport, device, bcsp_rate) < 0)
		exit(1);

	free(device);

	for (i = 0; commands[i].str; i++) {
		if (strcasecmp(commands[i].str, argv[0]))
			continue;

		err = commands[i].func(transport, argc, argv);

		transport_close(transport);

		if (err < 0) {
			fprintf(stderr, "Can't execute command: %s (%d)\n",
							strerror(errno), errno);
			exit(1);
		}

		exit(0);
	}

	fprintf(stderr, "Unsupported command\n");

	transport_close(transport);

	exit(1);
}
