/*
  Copyright 2006 Jean-Baptiste Note
  Greatly inspired by Samuel Ortiz <samuel.ortiz@nokia.com>

  This file is part of islsm.

  islsm 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.

  islsm 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 islsm; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

*/

#include <linux/kernel.h>
#include <linux/device.h>

#include "islsm_sysfs.h"
#include "isl_sm.h"
#include "islsm_log.h"
#include "islsm_protocol.h" /* only for eeprom length */

#ifdef CONFIG_SYSFS

/* firmware name */
static ssize_t islsm_show_fw_name(struct class_device *dev, char *buf)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	return sprintf(buf, "%.*s\n", ISLSM_FW_NAME_LEN, islsm->firmware_name);
}

static ssize_t islsm_store_fw_name(struct class_device *dev, const char *buf, size_t count)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	size_t min_len = min( (size_t)ISLSM_FW_NAME_LEN, count);
	char *firmware = islsm->firmware_name;
	/* we're not sure the input buf is zero-padded */
	strncpy(firmware, buf, min_len);
	firmware[min_len] = 0;
	return count;
}

/* FCS filtering option in monitor mode. probably the softmac should
 * handle this parameter */
static ssize_t islsm_show_rxfilter(struct class_device *dev, char *buf)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	return sprintf(buf, "%04x\n", islsm->soft_rxfilter);
}

static ssize_t islsm_store_rxfilter(struct class_device *dev, const char *buf, size_t count)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	short rxfilter;
	int matched;
	/* we're not sure the input buf is zero-ended ? Some drivers
	 * seem to assume so */
	/* buf[count-1] = 0; */
	matched = sscanf(buf, "%hx", &rxfilter);
	if (matched != 1)
		return -EINVAL;

	islsm->soft_rxfilter = rxfilter;
	return strlen(buf);
}

/*
 * EEPROM raw data
 */

static ssize_t
islsm_read_eeprom(struct kobject *kobj, char *buf, loff_t off, size_t count)
{
	struct class_device *dev = container_of(kobj,struct class_device,kobj);
	const struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	size_t eeprom_len = islsm->eeprom_size;
	u8 *eeprom = islsm->eeprom;

	if (!eeprom)
		return 0;

	if (off > eeprom_len)
		return 0;

	if (off + count > eeprom_len)
		count = eeprom_len - off;

	memcpy(buf, &eeprom[off], count);
	return count;
}

static struct bin_attribute sysfs_eeprom_dump_attr = {
	.attr = {
		.name = "eeprom",
		.mode = S_IRUSR,
		.owner = THIS_MODULE,
	},
	.size = 0,
	.read = islsm_read_eeprom,
};

/*
 * In-firmware tx retry count setting for outgoing data packets
 */

static ssize_t
islsm_store_data_txretry(struct class_device *dev,
			 const char *buf, size_t count) {
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	int value1, value2;
	int matched;
	matched = sscanf(buf, "%x %x", &value1, &value2);
	if (matched != 2 ||
	    value1 < 0 || value1 > 7 ||
	    value2 < 0 || value2 > 7)
		return -EINVAL;
	islsm->smpar.data.retry1 = value1;
	islsm->smpar.data.retry2 = value2;
	return strlen(buf);
}

static ssize_t
islsm_show_data_txretry(struct class_device *dev, char *buf) {
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	ssize_t count = 0;
	u8 retries1 = islsm->smpar.data.retry1 & 0xff;
	u8 retries2 = islsm->smpar.data.retry2 & 0xff;
	return sprintf(buf + count, "%02x %02x\n", retries1, retries2);
}

/*
 * Rate setting
 * FIXME: do this properly in ieee80211softmac
 */
#define TX_RATE_SIZE 8

static ssize_t
islsm_store_rateset(char dst[], const char *buf, size_t count) {
	int matched;
	/* would there be a way to write this with
	   a loop, more cleverly with simple_strtoul */
	matched = sscanf(buf, "%hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx",
			 &dst[0], &dst[1], &dst[2], &dst[3],
			 &dst[4], &dst[5], &dst[6], &dst[7]);
	if (matched == 0)
		return -EINVAL;
	return strlen(buf);
}

static ssize_t
islsm_print_rateset(char *buf, const char rate_table[]) {
	ssize_t count = 0;
	int i;
	for(i = 0; i < TX_RATE_SIZE; i++) {
		count += sprintf(buf + count, "%02x ", rate_table[i]);
	}
	count += sprintf(buf + count, "\n");
	return count;
}

static ssize_t islsm_store_data_rateset(struct class_device *dev,
					const char *buf, size_t count)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	char *rate_table = islsm->smpar.data.rateset;
	return islsm_store_rateset(rate_table, buf, count);
}

static ssize_t islsm_dump_data_rateset(struct class_device *dev, char *buf)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	char *rate_table = islsm->smpar.data.rateset;
	return islsm_print_rateset(buf, rate_table);
}

static ssize_t islsm_store_ctrl_rateset(struct class_device *dev,
					const char *buf, size_t count)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	char *rate_table = islsm->filter_rateset;
	/* Maybe also trigger the emission of the packet */
	return islsm_store_rateset(rate_table, buf, count);
}

static ssize_t islsm_dump_ctrl_rateset(struct class_device *dev, char *buf)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	char *rate_table = islsm->filter_rateset;
	/* FIXME: should trigger the resend of the rxfilter -- for now
	 * you need to set this before interface launch */
	return islsm_print_rateset(buf, rate_table);
}

/*
 * Export state of the allocator. Allows for simple tracking of the
 * allocator state.
 */
static ssize_t islsm_dump_allocstate(struct class_device *dev, char *buf)
{
	struct islsm *islsm = ISLSM_OF_CLASSDEV(dev);
	return islsm_alloc_dump(&islsm->memory, buf);
}

/* TODO: get back chopped pda information */

/* read-write parameters */
static CLASS_DEVICE_ATTR(fw_name, S_IRUGO | S_IWUSR, islsm_show_fw_name, islsm_store_fw_name);
static CLASS_DEVICE_ATTR(rxfilter, S_IRUGO | S_IWUSR, islsm_show_rxfilter, islsm_store_rxfilter);
static CLASS_DEVICE_ATTR(txretry, S_IRUGO | S_IWUSR, islsm_show_data_txretry, islsm_store_data_txretry);
static CLASS_DEVICE_ATTR(datarateset, S_IRUGO | S_IWUSR, islsm_dump_data_rateset, islsm_store_data_rateset);
static CLASS_DEVICE_ATTR(ctrlrateset, S_IRUGO | S_IWUSR, islsm_dump_ctrl_rateset, islsm_store_ctrl_rateset);

/* read-only data */
static CLASS_DEVICE_ATTR(allocator, S_IRUGO, islsm_dump_allocstate, NULL);

static struct class_device_attribute *islsm_attrs[] = {
	&class_device_attr_fw_name,
	&class_device_attr_rxfilter,
	&class_device_attr_txretry,
	&class_device_attr_datarateset,
 	&class_device_attr_ctrlrateset,
	&class_device_attr_allocator,
	NULL,
};

static int
islsm_device_create_files(struct class_device *dev) {
	int i = 0;
	int err = 0;
	while(!err && islsm_attrs[i])
		err = class_device_create_file(dev,islsm_attrs[i++]);
	return err;
}

static void
islsm_device_remove_files(struct class_device *dev) {
	int i = 0;
	while(islsm_attrs[i])
		class_device_remove_file(dev,islsm_attrs[i++]);
}

int islsm_sysfs_create_files(struct class_device *dev) {
	int err;

	islog(L_DEBUG, "Creating islsm sysfs\n");
	err = class_device_create_bin_file(dev, &sysfs_eeprom_dump_attr);
	if (err)
		goto out;
	err = islsm_device_create_files(dev);
 out:
	return err;
}

/* check whether cleanup is automatic or not */
void islsm_sysfs_remove_files(struct class_device *dev)
{
	islog(L_DEBUG, "Removing islsm sysfs\n");
	class_device_remove_bin_file(dev, &sysfs_eeprom_dump_attr);
	islsm_device_remove_files(dev);
}

#endif /* CONFIG_SYSFS */
