/*
 * linux/drivers/cpufreq/proc_intf.c
 *
 * Copyright (C) 2002 - 2003 Dominik Brodowski
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/ctype.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>


#define CPUFREQ_ALL_CPUS		((NR_CPUS))

/**
 * cpufreq_parse_policy - parse a policy string
 * @input_string: the string to parse.
 * @policy: the policy written inside input_string
 *
 * This function parses a "policy string" - something the user echo'es into
 * /proc/cpufreq or gives as boot parameter - into a struct cpufreq_policy.
 * If there are invalid/missing entries, they are replaced with current
 * cpufreq policy.
 */
static int cpufreq_parse_policy(char input_string[42], struct cpufreq_policy *policy)
{
	unsigned int            min = 0;
	unsigned int            max = 0;
	unsigned int            cpu = 0;
	char			str_governor[16];
	struct cpufreq_policy   current_policy;
	unsigned int            result = -EFAULT;

	if (!policy)
		return -EINVAL;

	policy->min = 0;
	policy->max = 0;
	policy->policy = 0;
	policy->cpu = CPUFREQ_ALL_CPUS;

	if (sscanf(input_string, "%d:%d:%d:%15s", &cpu, &min, &max, str_governor) == 4) 
	{
		policy->min = min;
		policy->max = max;
		policy->cpu = cpu;
		result = 0;
		goto scan_policy;
	}
	if (sscanf(input_string, "%d%%%d%%%d%%%15s", &cpu, &min, &max, str_governor) == 4)
	{
		if (!cpufreq_get_policy(&current_policy, cpu)) {
			policy->min = (min * current_policy.cpuinfo.max_freq) / 100;
			policy->max = (max * current_policy.cpuinfo.max_freq) / 100;
			policy->cpu = cpu;
			result = 0;
			goto scan_policy;
		}
	}

	if (sscanf(input_string, "%d:%d:%15s", &min, &max, str_governor) == 3) 
	{
		policy->min = min;
		policy->max = max;
		result = 0;
		goto scan_policy;
	}

	if (sscanf(input_string, "%d%%%d%%%15s", &min, &max, str_governor) == 3)
	{
		if (!cpufreq_get_policy(&current_policy, cpu)) {
			policy->min = (min * current_policy.cpuinfo.max_freq) / 100;
			policy->max = (max * current_policy.cpuinfo.max_freq) / 100;
			result = 0;
			goto scan_policy;
		}
	}

	return -EINVAL;

scan_policy:
	result = cpufreq_parse_governor(str_governor, &policy->policy, &policy->governor);

	return result;
}

/**
 * cpufreq_proc_read - read /proc/cpufreq
 *
 * This function prints out the current cpufreq policy.
 */
static int cpufreq_proc_read (
	char			*page,
	char			**start,
	off_t			off,
	int 			count,
	int 			*eof,
	void			*data)
{
	char			*p = page;
	int			len = 0;
	struct cpufreq_policy   policy;
	unsigned int            min_pctg = 0;
	unsigned int            max_pctg = 0;
	unsigned int            i = 0;

	if (off != 0)
		goto end;

	p += sprintf(p, "          minimum CPU frequency  -  maximum CPU frequency  -  policy\n");
	for (i=0;i<NR_CPUS;i++) {
		if (!cpu_online(i))
			continue;

		if (cpufreq_get_policy(&policy, i))
			continue;

		if (!policy.cpuinfo.max_freq)
			continue;

		min_pctg = (policy.min * 100) / policy.cpuinfo.max_freq;
		max_pctg = (policy.max * 100) / policy.cpuinfo.max_freq;

		p += sprintf(p, "CPU%3d    %9d kHz (%3d %%)  -  %9d kHz (%3d %%)  -  ",
			     i , policy.min, min_pctg, policy.max, max_pctg);
		if (policy.policy) {
			switch (policy.policy) {
				case CPUFREQ_POLICY_POWERSAVE:
				p += sprintf(p, "powersave\n");
				break;	
			case CPUFREQ_POLICY_PERFORMANCE:
				p += sprintf(p, "performance\n");
				break;
			default:
				p += sprintf(p, "INVALID\n");
				break;
			} 
		} else
			p += snprintf(p, CPUFREQ_NAME_LEN, "%s\n", policy.governor->name);
	}
end:
	len = (p - page);
	if (len <= off+count) 
		*eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) 
		len = count;
	if (len<0) 
		len = 0;

	return len;
}


/**
 * cpufreq_proc_write - handles writing into /proc/cpufreq
 *
 * This function calls the parsing script and then sets the policy
 * accordingly.
 */
static int cpufreq_proc_write (
        struct file		*file,
        const char		*buffer,
        unsigned long		count,
        void			*data)
{
	int                     result = 0;
	char			proc_string[42] = {'\0'};
	struct cpufreq_policy   policy;
	unsigned int            i = 0;


	if ((count > sizeof(proc_string) - 1))
		return -EINVAL;
	
	if (copy_from_user(proc_string, buffer, count))
		return -EFAULT;
	
	proc_string[count] = '\0';

	result = cpufreq_parse_policy(proc_string, &policy);
	if (result)
		return -EFAULT;

	if (policy.cpu == CPUFREQ_ALL_CPUS)
	{
		for (i=0; i<NR_CPUS; i++) 
		{
			policy.cpu = i;
			if (cpu_online(i))
				cpufreq_set_policy(&policy);
		}
	} 
	else
		cpufreq_set_policy(&policy);

	return count;
}


/**
 * cpufreq_proc_init - add "cpufreq" to the /proc root directory
 *
 * This function adds "cpufreq" to the /proc root directory.
 */
static int __init cpufreq_proc_init (void)
{
	struct proc_dir_entry *entry = NULL;

	/* are these acceptable values? */
	entry = create_proc_entry("cpufreq", S_IFREG|S_IRUGO|S_IWUSR, 
				  &proc_root);

	if (!entry) {
		printk(KERN_ERR "unable to create /proc/cpufreq entry\n");
		return -EIO;
	} else {
		entry->read_proc = cpufreq_proc_read;
		entry->write_proc = cpufreq_proc_write;
	}

	return 0;
}


/**
 * cpufreq_proc_exit - removes "cpufreq" from the /proc root directory.
 *
 * This function removes "cpufreq" from the /proc root directory.
 */
static void __exit cpufreq_proc_exit (void)
{
	remove_proc_entry("cpufreq", &proc_root);
	return;
}

MODULE_AUTHOR ("Dominik Brodowski <linux@brodo.de>");
MODULE_DESCRIPTION ("CPUfreq /proc/cpufreq interface");
MODULE_LICENSE ("GPL");

module_init(cpufreq_proc_init);
module_exit(cpufreq_proc_exit);
