/*
 * fan_policy.c -- code for fan policy
 * 
 * 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, 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.
 *
 * Written by Soós Péter <sp@osb.hu>, 2002-2003
 */


#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/proc_fs.h>

#include <asm/system.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#ifdef OMNIBOOK_STANDALONE
#include "omnibook.h"
#else
#include <linux/omnibook.h>
#endif

#include "ec.h"

static struct proc_dir_entry *proc_fan_policy;

int omnibook_get_fan_policy(u8 (*fan)[OMNIBOOK_FAN_LEVELS])
{
	int retval = 0;
	int i;
	u8 tmp;
	
	switch (omnibook_ectype) {
	case XE3GF:
		for (i = 0; i <= OMNIBOOK_FAN_LEVELS; i++) {
			retval = omnibook_ec_read(XE3GF_FOT + i, &tmp);
			if (retval)
				return retval;
			(*fan)[i] = tmp;
		}
		break;
	default:
		printk(KERN_INFO "%s: Fan policy is unsupported on this machine.\n", MODULE_NAME);
		retval = -ENODEV;
	}

	return retval;
}

int omnibook_set_fan_policy(u8 (*fan)[OMNIBOOK_FAN_LEVELS])
{
	int retval;
	int i;
	
	switch (omnibook_ectype) {
	case XE3GF:
		if ((*fan)[0] > OMNIBOOK_FOT_MAX)
			return -EINVAL;
		for (i = 0; i < OMNIBOOK_FAN_LEVELS; i++) {
			if (((*fan)[i] > (*fan)[i + 1]) || ((*fan)[i] < OMNIBOOK_FAN_MIN) || ((*fan)[i] > OMNIBOOK_FAN_MAX))
				return -EINVAL;
			if ((*fan)[i + 1] > OMNIBOOK_FAN_MAX)
				return -EINVAL;
		}
		for (i = 0; i <= OMNIBOOK_FAN_LEVELS; i++) {
			retval = omnibook_ec_write(XE3GF_FOT + i, (*fan)[i]);
			if (retval)
				return retval;
		}
		break;
	default:
		printk(KERN_INFO "%s: Fan policy is unsupported on this machine.\n", MODULE_NAME);
		retval = -ENODEV;
	}

	return retval;
}

int omnibook_set_fan_policy_defaults(void)
{
	int retval;
	int i;
	u8 fan_defaults[] = {
		OMNIBOOK_FOT_DEFAULT,
		OMNIBOOK_FAN1_DEFAULT,
		OMNIBOOK_FAN2_DEFAULT,
		OMNIBOOK_FAN3_DEFAULT,
		OMNIBOOK_FAN4_DEFAULT,
		OMNIBOOK_FAN5_DEFAULT,
		OMNIBOOK_FAN6_DEFAULT,
		OMNIBOOK_FAN7_DEFAULT,
	};

	switch (omnibook_ectype) {
	case XE3GF:
		for (i = 0; i <= OMNIBOOK_FAN_LEVELS; i++) {
			retval = omnibook_ec_write(XE3GF_FOT + i, fan_defaults[i]);
			if (retval)
				return retval;
		}
		break;
	default:
		printk(KERN_INFO "%s: Fan policy is unsupported on this machine.\n", MODULE_NAME);
		retval = -ENODEV;
	}

	return retval;
}

static int omnibook_proc_fan_policy_status(char *buffer, char **start, off_t off, int count, int *eof, void *data)
{
	int retval;
	int len = 0;
	char *b = buffer;
	u8 fan[OMNIBOOK_FAN_LEVELS];
	u8 i;
	
	retval = omnibook_get_fan_policy(&fan);
	if (retval)
		return retval;

	b += sprintf(b, "Fan off temperature:        %2d C\n", fan[0]);
	for (i = 1; i <= OMNIBOOK_FAN_LEVELS; i++) {
		b += sprintf(b, "Fan level %1d temperature:    %2d C\n", i, fan[i]);
	}
	b += sprintf(b, "Minimal temperature to set: %2d C\n", OMNIBOOK_FAN_MIN);
	b += sprintf(b, "Maximal temperature to set: %2d C\n", OMNIBOOK_FAN_MAX);

	len = b - buffer;
	if (len <= off + count)
		*eof = 1;
	*start = buffer + off;
	len -= off;
	if (len > count)
		len = count;
	if (len < 0)
		len = 0;
	return len;
}

static int omnibook_proc_fan_policy_set(struct file *file, const char *buffer, unsigned long count, void *data)
{
	int len = (count < OMNIBOOK_FAN_POLICY_STRING) ? count : OMNIBOOK_FAN_POLICY_STRING;
	char status[OMNIBOOK_FAN_POLICY_STRING] = {'\0'};
	char *b;
	char *prev;
	u8 n;
	u8 c;
	u8 fan[OMNIBOOK_FAN_LEVELS];
	int retval;

	retval = omnibook_get_fan_policy(&fan);
	if (retval)
		return retval;

	if (copy_from_user(status, buffer, len))
		return -EFAULT;
	status[len] = 0;
	b = status;
	n = 0;
	c = 0;
	prev = NULL;
	do {
		printk(KERN_INFO "%s: c=%d\n", MODULE_NAME, c);
		if (n > OMNIBOOK_FAN_LEVELS)
			return -EINVAL;
		if (*b >= '0' && *b <= '9')
			c = c * 10 + (*b - '0');
		else if (((*b == ' ') || (*b == '\0') || (*b == '\n')) && (*prev >= '0') && (*prev <= '9')) {
			fan[n] = c;
			n++;
			c = 0;
		} else {
			return -EINVAL;
		}
		prev = b;
		b++;
	} while ((*prev != '\n') && (*prev != '\0'));

	/* A zero value set the defaults */ 
	if ((fan[0] == 0) && (n == 1)) {
		retval = omnibook_set_fan_policy_defaults();
		if (retval)
			return retval;
	} else
		omnibook_set_fan_policy(&fan);

	return count;
}

int __init omnibook_fan_policy_init(void)
{
	mode_t pmode;

	switch (omnibook_ectype) {
	case XE3GF:
		pmode = S_IFREG | S_IWUSR | S_IRUGO;
		if (omnibook_userset)
			pmode = pmode | S_IWUGO;
		proc_fan_policy = create_proc_entry("fan_policy", pmode, omnibook_proc_root);
		break;
	default:
		printk(KERN_INFO "%s: Fan policy is unsupported on this machine.\n", MODULE_NAME);
		return 0;
	}
	if (proc_fan_policy) {
			proc_fan_policy->read_proc = omnibook_proc_fan_policy_status;
			proc_fan_policy->write_proc = omnibook_proc_fan_policy_set;
	} else {
		printk(KERN_ERR "%s: Unable to create /proc/%s/fan_policy.\n", MODULE_NAME, MODULE_NAME);
		return -ENOENT;
	}
	printk(KERN_INFO "%s: Fan policy setting is enabled.\n", MODULE_NAME);
	return 0;
}

void __exit omnibook_fan_policy_cleanup(void)
{
	if (proc_fan_policy)
		remove_proc_entry("fan_policy", omnibook_proc_root);
}

EXPORT_SYMBOL(omnibook_get_fan_policy);
EXPORT_SYMBOL(omnibook_set_fan_policy);
EXPORT_SYMBOL(omnibook_set_fan_policy_defaults);

/* End of file */
