// directec.c
// (C) 2003 Ulrich Hecht
// released under the terms of the GNU Public License v2

// reads the battery state on a Samsung V25 (and possibly other notebooks using the same data
// structures) via the embedded controller without using ACPI

// see drivers/acpi/ec.c for Embedded Controller comms, drivers/acpi.battery.c for battery data structs,
// samsungv25.dsl for hardware addresses

#include <sys/io.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <stdlib.h>

// Embedded controller I/O ports
// check your ACPI DSDT disassembly for the _CRS method of your embedded controller, named H_EC in
// the case of the Samsung V25
#define EC_STATUS 0x66	// EC status port
#define EC_COMMAND 0x66 // EC command port
#define EC_DATA 0x62	// EC data port

// types of values
#define T_BIT	0x80	// single bit, add number of bit (e.g. T_BIT+1 for second bit)
#define T_BYTE	0x40	// unsigned byte
#define T_WORD	0x20	// unsigned 16-bit word

// units
#define U_ONOFF 0x01	// 1 is on, 0 is off
#define U_YESNO 0x02	// 1 is yes, 0 is no
#define U_MA	0x03	// current, mA
#define U_MV	0x04	// voltage, mV
#define U_PCT	0x05	// percentage
#define U_MAH	0x06	// capacity, mAh
#define U_DEGC  0x07	// temperature, degrees centigrade; this may be different on your machine. The Samsung
			// V25 ACPI code converts this value to dK (no, really!) as required by the ACPI standard
#define U_CRTOK 0x08	// 0 is OK, 1 is critical
#define U_MIN	0x09	// minutes
#define U_NA	0x80	// not applicable

// format for value entries
typedef struct
{
	uint8_t port;	// address of the embedded controller register
	uint8_t type;	// type of value
	char* desc;	// textual description
	uint8_t unit;	// unit of value
} batterydata_t;
	
// the values
batterydata_t bd[]={
	{0x80,T_BIT+0,"battery 1 present (B1EX)",U_YESNO},
	{0x80,T_BIT+1,"battery 2 present (B2EX)",U_YESNO},
	{0x80,T_BIT+2,"AC on-line (ACEX)",U_YESNO},
	{0x81,T_BIT+0,"SWBE",U_NA},
	{0x81,T_BIT+1,"DCBE",U_NA},
	{0x82,T_BIT+0,"RFON",U_NA},
	{0x82,T_BIT+1,"SPDI",U_NA},
	{0x83,T_BIT+0,"lid open (LIDS)",U_YESNO},
	{0x84,T_BYTE,"battery status (B1ST)",U_NA},		// acpi_battery_status.state
	{0x84,T_BIT+0,"discharging (B1ST & 1)",U_YESNO},
	{0x84,T_BIT+1,"charging (B1ST & 2)",U_YESNO},
	{0x84,T_BIT+2,"capacity state (B1ST & 4)",U_CRTOK},
	{0x85,T_BYTE,"battery status (B2ST)",U_NA},		// acpi_battery_status.state
	{0x85,T_BIT+0,"discharging (B2ST & 1)",U_YESNO},
	{0x85,T_BIT+1,"charging (B2ST & 2)",U_YESNO},
	{0x85,T_BIT+2,"capacity state (B2ST & 4)",U_CRTOK},
	{0xa0,T_WORD,"remaining percentage (B1RP)",U_PCT},
	{0xa2,T_WORD,"remaining capacity (B1RA)",U_MAH},	// acpi_battery_status.remaining_capacity
	{0xa4,T_WORD,"present rate (B1PR)",U_MA},	// identical to B1CR?
	{0xa6,T_WORD,"present voltage (B1VO)",U_MV},
	{0xa8,T_WORD,"remaining percentage (B2RP)",U_PCT},
	{0xaa,T_WORD,"remaining capacity (B2RA)",U_MAH},
	{0xac,T_WORD,"present rate (B2PR)",U_MA},	// identical to B2CR?
	{0xae,T_WORD,"present voltage (B2VO)",U_MV},
	{0xb0,T_WORD,"design capacity (B1DA)",U_MAH},	// acpi_battery_info.design_capacity
	{0xb2,T_WORD,"last full capacity (B1DF)",U_MAH},
	{0xb4,T_WORD,"design voltage (B1DV)",U_MV},
	{0xb6,T_WORD,"design capacity low (B1DL)",U_MAH},
	{0xb8,T_WORD,"design capacity (B2DA)",U_MAH},
	{0xba,T_WORD,"last full capacity (B2DF)",U_MAH},
	{0xbc,T_WORD,"design voltage (B2DV)",U_MV},
	{0xbe,T_WORD,"B2DL",U_NA},
	{0xc0,T_BYTE,"temperature (CTMP)",U_DEGC},
	{0xd0,T_WORD,"battery time left (B1TI)",U_MIN},
	{0xd2,T_WORD,"B1SE",U_NA},
	{0xd4,T_WORD,"present rate (B1CR)",U_MA},	// acpi_battery_status.present_rate; Samsung V25 ACPI code
							// performs an odd conversion on this if value exceeds
							// 0x7fff, but this never happens on my machine
	{0xd6,T_WORD,"B1TM",U_NA},
	{0xd8,T_WORD,"B2TI",U_NA},
	{0xda,T_WORD,"B2SE",U_NA},
	{0xdc,T_WORD,"B2CR",U_NA},
	{0xde,T_WORD,"B2TM",U_NA},
	{0xe0,T_WORD,"B1NM",U_NA},
	{0xf0,T_WORD,"B2NM",U_NA},
	{0,0,NULL,0}
};

static void udelay(int delay)
{
	struct timeval otv;
	struct timeval tv;
	struct timezone tz;
	gettimeofday(&otv,&tz);
	gettimeofday(&tv,&tz);
	while((tv.tv_sec-otv.tv_sec)*1000000+tv.tv_usec-otv.tv_usec<delay)
		gettimeofday(&tv,&tz);
}

static uint8_t get_ec_byte(uint8_t address)
{
	int timeout;
again:
	timeout=100;
	outb(0x80,EC_COMMAND);	// ACPI_EC_COMMAND_READ
	while((inb(EC_STATUS)&2)&&timeout)	// wait for input buffer empty
	{
		udelay(100);
		timeout--;
	}
	if(!timeout)
		{ udelay(100); goto again; }
	outb(address,EC_DATA);	// write address
	timeout=100;
	while((!(inb(EC_STATUS)&1))&&timeout)	// wait for output buffer full
	{
		udelay(100);
		timeout--;
	}
	if(!timeout)
		{ udelay(100); goto again; }
	return inb(EC_DATA);
}

static uint16_t get_ec_word(uint8_t address)
{
	return (get_ec_byte(address)<<8)+get_ec_byte(address+1);
}

int main(int argc, char** argv)
{
	uint16_t value=0;
	int i;
	if(ioperm(EC_COMMAND,1,1)||ioperm(EC_DATA,1,1))
	{
		perror("ioperm");
		exit(-1);
	}
	setuid(getuid());
	for(i=0;bd[i].type;i++)
	{
		// only show those values who's description contains the command line argument
		if(argc>1 && !strstr(bd[i].desc,argv[1])) continue;
		
		if(bd[i].type&(T_BIT|T_BYTE))	// read one byte
		{
			value=get_ec_byte(bd[i].port);
			if(bd[i].type&T_BIT)
				value=(value&(1<<(bd[i].type&(0x7f))))?1:0;
		}
		else				// read two bytes
		{
			value=get_ec_word(bd[i].port);
			if(value==0xffff) continue;	// value not available
		}
		printf("%s: \t",bd[i].desc);
		switch(bd[i].unit)
		{
			case U_ONOFF:
				printf("%s",value?"on":"off");
				break;
			case U_YESNO:
				printf("%s",value?"yes":"no");
				break;
			case U_MA:
				printf("%d mA",value);
				break;
			case U_MV:
				printf("%d mV",value);
				break;
			case U_PCT:
				printf("%d%%",value);
				break;
			case U_MAH:
				printf("%d mAh",value);
				break;
			case U_DEGC:
				printf("%d °C",value);
				break;
			case U_CRTOK:
				printf("%s",value?"critical":"OK");
				break;
			case U_MIN:
				printf("%d min",value);
				break;
			default:
				printf("%d (0x%x)",value,value);
		}
		puts("");
	}
	return value;
}
