/*
 * battery.c -- code to handle battery status related functions
 * 
 * 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"

struct proc_dir_entry *proc_battery;

/*
 * Get static battery information
 * All info have to be reread every time because battery sould be cahnged
 * when laptop is on AC power 
 * return values:
 *  0 - OK
 *  1 - Battery is not present
 *  2 - Not supported
 */
int omnibook_get_battery_info(int num, struct omnibook_battery_info *battinfo)
{
	int retval;
	int i;
	u8 bat;
	u8 mask;
	u32 offset;

	switch (omnibook_ectype) {
	case XE3GF:
		if (num >= 2)
			return -EINVAL;
		offset = 0x0F;
		retval = omnibook_ec_read(XE3GF_BAL, &bat);
		if (retval)
			return retval;
		mask = XE3GF_BAL0_MASK;
		for (i = 0; i < num; i++)
			mask = mask << 1;
		if (bat & mask) {
			retval = omnibook_ec_read(XE3GF_BTY0 + offset * i, &(*battinfo).type);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GF_BSN0 + offset * i, &(*battinfo).sn);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GF_BDV0 + offset * i, &(*battinfo).dv);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GF_BDC0 + offset * i, &(*battinfo).dc);
			if (retval)
				return retval;

			(*battinfo).type = ((*battinfo).type & XE3GF_BTY_MASK) ? 1 : 0;
		} else
			return 1;
		break;
	case XE3GC:
		if (num >= 2)
			return -EINVAL;
		offset = 0x0F;
		retval = omnibook_ec_read(XE3GC_BAT, &bat);
		if (retval)
			return retval;
		mask = XE3GC_BAT0_MASK;
		for (i = 0; i < num; i++)
			mask = mask << 1;
		if (bat & mask) {
			retval = omnibook_ec_read16(XE3GC_BDV0 + offset * i, &(*battinfo).dv);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GC_BDC0 + offset * i, &(*battinfo).dc);
			if (retval)
				return retval;
			retval = omnibook_ec_read(XE3GC_BTY0 + offset * i, &(*battinfo).type);
			if (retval)
				return retval;

			(*battinfo).type = ((*battinfo).type & XE3GC_BTY_MASK) ? 1 : 0;
			(*battinfo).sn = 0; /* Unknown */
		} else
			return 1;
		break;
	/* FIXME */
	case OB500:
	case OB510:
		switch (num) {
		case 0:
			break;
		case 1:
			break;
		case 2:
			break;
		default:
			return -EINVAL;
		}
		break;
	case OB6000:
	case OB6100:
	case XE4500:
		switch (num) {
		case 0:
			break;
		case 1:
			break;
		default:
			return -EINVAL;
		}
		break;
	default:
		return 2;
	}
	return 0;
}

/*
 * Get battery status
 * return values:
 *  0 - OK
 *  1 - Battery is not present
 *  2 - Not supported
 */
int omnibook_get_battery_status(int num, struct omnibook_battery_status *battstat)
{
	int retval;
	int i;
	u8 bat;
	u8 mask;
	u8 status;
	u16 dc;
	int gauge;
	u8 offset;

	switch (omnibook_ectype) {
	case XE3GF:
		if (num >= 2)
			return -EINVAL;
		offset = 0x0F;
		retval = omnibook_ec_read(XE3GF_BAL, &bat);
		if (retval)
			return retval;
		mask = XE3GF_BAL0_MASK;
		for (i = 0; i < num; i++)
			mask = mask << 1;
		if (bat & mask) {
			retval = omnibook_ec_read(XE3GF_BST0 + offset * i, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GF_BRC0 + offset * i, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GF_BPV0 + offset * i, &(*battstat).pv);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GF_BFC0 + offset * i, &(*battstat).lc);
			if (retval)
				return retval;
			retval = omnibook_ec_read(XE3GF_GAU0 + offset * i, &(*battstat).gauge);
			if (retval)
				return retval;

			if (status & XE3GF_BST_MASK_CRT)
				(*battstat).status = OMNIBOOK_BATTSTAT_CRITICAL;
			else if (status & XE3GF_BST_MASK_CHR)
				(*battstat).status = OMNIBOOK_BATTSTAT_CHARGING;
			else if (status & XE3GF_BST_MASK_DSC)
				(*battstat).status = OMNIBOOK_BATTSTAT_DISCHARGING;
			else if (status & (XE3GF_BST_MASK_CHR | XE3GF_BST_MASK_DSC))
				(*battstat).status = OMNIBOOK_BATTSTAT_UNKNOWN;
			else {
				(*battstat).status = OMNIBOOK_BATTSTAT_CHARGED;
			}
		} else
			return 1;
		break;
	case XE3GC:
		if (num >= 2)
			return -EINVAL;
		offset = 0x0F;
		retval = omnibook_ec_read(XE3GC_BAT, &bat);
		if (retval)
			return retval;
		mask = XE3GC_BAT0_MASK;
		for (i = 0; i < num; i++)
			mask = mask << 1;
		if (bat & mask) {
			retval = omnibook_ec_read(XE3GC_BST0 + offset * i, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GC_BRC1 + offset * i, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GC_BPV0 + offset * i, &(*battstat).pv);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(XE3GC_BDC0 + offset * i, &dc);
			if (retval)
				return retval;

			if (status & XE3GC_BST_MASK_CRT)
				(*battstat).status = OMNIBOOK_BATTSTAT_CRITICAL;
			else if (status & XE3GC_BST_MASK_CHR)
				(*battstat).status = OMNIBOOK_BATTSTAT_CHARGING;
			else if (status & XE3GC_BST_MASK_DSC)
				(*battstat).status = OMNIBOOK_BATTSTAT_DISCHARGING;
			else if (status & (XE3GC_BST_MASK_CHR | XE3GC_BST_MASK_DSC))
				(*battstat).status = OMNIBOOK_BATTSTAT_UNKNOWN;
			else {
				(*battstat).status = OMNIBOOK_BATTSTAT_CHARGED;
			}
			gauge = ((*battstat).rc * 100) / dc;
			(*battstat).gauge = gauge;
			(*battstat).lc = 0; /* Unknown */
		} else
			return 1;
	case OB500:
	case OB510:
		switch (num) {
		case 0:
			retval = omnibook_ec_read(OB500_BT1S, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT1C, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT1V, &(*battstat).pv);
			if (retval)
				return retval;
			break;
		case 1:
			retval = omnibook_ec_read(OB500_BT2S, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT2C, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT2V, &(*battstat).pv);
			if (retval)
				return retval;
			break;
		case 2:
			retval = omnibook_ec_read(OB500_BT3S, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT3C, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT3V, &(*battstat).pv);
			if (retval)
				return retval;
			break;
		default:
			return -EINVAL;
		}
		if (status & OB500_BST_MASK_CRT)
			(*battstat).status = OMNIBOOK_BATTSTAT_CRITICAL;
		else if (status & OB500_BST_MASK_CHR)
			(*battstat).status = OMNIBOOK_BATTSTAT_CHARGING;
		else if (status & OB500_BST_MASK_DSC)
			(*battstat).status = OMNIBOOK_BATTSTAT_DISCHARGING;
		else if (status & (OB500_BST_MASK_CHR | OB500_BST_MASK_DSC))
			(*battstat).status = OMNIBOOK_BATTSTAT_UNKNOWN;
		else {
			(*battstat).status = OMNIBOOK_BATTSTAT_CHARGED;
		}
		break;
	case OB6000:
	case OB6100:
	case XE4500:
		switch (num) {
		case 0:
			retval = omnibook_ec_read(OB500_BT1S, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT1C, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT1V, &(*battstat).pv);
			if (retval)
				return retval;
			break;
		case 1:
			retval = omnibook_ec_read(OB500_BT3S, &status);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT3C, &(*battstat).rc);
			if (retval)
				return retval;
			retval = omnibook_ec_read16(OB500_BT3V, &(*battstat).pv);
			if (retval)
				return retval;
			break;
		default:
			return -EINVAL;
		}
		if (status & OB500_BST_MASK_CRT)
			(*battstat).status = OMNIBOOK_BATTSTAT_CRITICAL;
		else if (status & OB500_BST_MASK_CHR)
			(*battstat).status = OMNIBOOK_BATTSTAT_CHARGING;
		else if (status & OB500_BST_MASK_DSC)
			(*battstat).status = OMNIBOOK_BATTSTAT_DISCHARGING;
		else if (status & (OB500_BST_MASK_CHR | OB500_BST_MASK_DSC))
			(*battstat).status = OMNIBOOK_BATTSTAT_UNKNOWN;
		else {
			(*battstat).status = OMNIBOOK_BATTSTAT_CHARGED;
		}
		break;
	default:
		return 2;
	}
	return 0;
}

static int omnibook_proc_battery(char *buffer, char **start, off_t off, int count, int *eof, void *data)
{
	char *statustr;
	char *typestr;
	int max = 0;
	int num = 0; 
	int retval;
	char *b = buffer;
	int len;
	int i;
	struct omnibook_battery_info battinfo;
	struct omnibook_battery_status battstat;

	switch (omnibook_ectype) {
	case XE3GF:
	case XE3GC:
	case OB6000:
	case XE4500:
		max = 2;
		break;
	case OB500:
	case OB510:
		max = 3;
		break;
	}
	
	for (i = 0; i < max; i++) {
		retval = omnibook_get_battery_info(i, &battinfo);
		if (retval == 0) {
			num++;
			omnibook_get_battery_status(i, &battstat);
			typestr = (battinfo.type) ? "Li-Ion" : "NiMH";
			switch (battstat.status) {
			case OMNIBOOK_BATTSTAT_CHARGED:
				statustr = "charged";
				break;
			case OMNIBOOK_BATTSTAT_DISCHARGING:
				statustr = "discharging";
				break;
			case OMNIBOOK_BATTSTAT_CHARGING:
				statustr = "charging";
				break;
			case OMNIBOOK_BATTSTAT_CRITICAL:
				statustr = "critical";
				break;
			default:
				statustr = "unknown";
			}

			b += sprintf(b, "Battery:            %11d\n", i);
			b += sprintf(b, "Type:               %11s\n", typestr);
			if (battinfo.sn)
				b += sprintf(b, "Serial Number:      %11d\n", battinfo.sn);
			b += sprintf(b, "Present Voltage:    %11d mV\n", battstat.pv);
			b += sprintf(b, "Design Voltage:     %11d mV\n", battinfo.dv);
			b += sprintf(b, "Remaining Capacity: %11d mAh\n", battstat.rc);
			if (battstat.lc)
				b += sprintf(b, "Last Full Capacity: %11d mAh\n", battstat.lc);
			b += sprintf(b, "Design Capacity:    %11d mAh\n", battinfo.dc);
			b += sprintf(b, "Gauge:              %11d %%\n", battstat.gauge);
			b += sprintf(b, "Status:             %11s\n", statustr);
			b += sprintf(b, "\n");
		}
	}
	if (num == 0)
		b += sprintf(b, "No battery present\n");

	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;
}

int __init omnibook_battery_init(void)
{
	mode_t pmode;

	switch (omnibook_ectype) {
	case XE3GF:
	case XE3GC:
	/* FIXME */
//	case OB500:
//	case OB510:
//	case OB6000:
//	case OB6100:
//	case XE4500:
		pmode = S_IFREG | S_IRUGO;
		proc_battery = create_proc_read_entry("battery", pmode, omnibook_proc_root, omnibook_proc_battery, NULL);
		break;
	default:
		printk(KERN_INFO "%s: Battery status monitoring is unsupported on this machine.\n", MODULE_NAME);
		return 0;
	}
	if (! proc_battery) {
		printk(KERN_ERR "%s: Unable to create /proc/%s/battery.\n", MODULE_NAME, MODULE_NAME);
		return -ENOENT;
	}
	printk(KERN_INFO "%s: Battery status monitoring is enabled.\n", MODULE_NAME);
	return 0;
}

void __exit omnibook_battery_cleanup(void)
{
	if (proc_battery)
		remove_proc_entry("battery", omnibook_proc_root);
}

EXPORT_SYMBOL(omnibook_get_battery_info);
EXPORT_SYMBOL(omnibook_get_battery_status);

/* End of file */
