/*
 * asmi.c --- routines that talk the ASMI protocol.
 */

#include <stdio.h>

static inline void outb(unsigned char value, unsigned short port)
{
__asm__ __volatile__ ("outb %b0,%w1"
		: /* no outputs */
		:"a" (value),"d" (port));
}

static inline unsigned int inb(unsigned short port)
{
	unsigned int _v;
__asm__ __volatile__ ("inb %w1,%b0"
		:"=a" (_v):"d" (port),"0" (0));
	return _v;
}

static inline void outw(unsigned short value, unsigned short port) {
  asm volatile ("outw %w0,%w1" : :"a" (value),"d" (port));
}

static inline unsigned int inw (unsigned short port) {
  unsigned _v;
  asm volatile ("inw %w1,%w0" :"=a" (_v):"d" (port),"0" (0));
  return _v;
}

#define IDXLCK		0xfa
#define CFGINDEX	0x24
#define CFGDATA		0x25

#define ASMI_ADDRL	0x84
#define ASMI_ADDRH	0x85
#define SYS_EVNT_CFG2	0xb5

static inline void open_360sl () {
  (void) inb (0xfc23);
  (void) inb (0xf023);
  (void) inb (0xc023);
  (void) inb (0x0023);
}

static unsigned char read_360sl (unsigned char port) {
  outb (port, CFGINDEX);
  return inb (CFGDATA);
}

static void write_360sl (unsigned char port, unsigned char data) {
  outb (port, CFGINDEX);
  outb (data, CFGDATA);
}

static inline void close_360sl () { write_360sl (IDXLCK, 1); }

static int get_asmi_port () {
	unsigned char low, high;
	unsigned char cfg2;
	int port;
	
	open_360sl ();

	low = read_360sl (ASMI_ADDRL);
	high = read_360sl (ASMI_ADDRH);
	cfg2 = read_360sl (SYS_EVNT_CFG2);
	close_360sl ();
	port = low + 256 * high;
	if (cfg2 & 4)
		return port;
	else
		return -1;
}

static int asmi_port = 0;

int asmi(int func, int device, int arg,
	 unsigned int *aret, unsigned int *bret, unsigned int *cret)
{
	int carry;
	unsigned int ax, bx, cx, dx;
	
	if (!asmi_port)
		asmi_port = get_asmi_port();
	
	if (asmi_port < 0)
		return -1;

	ax = func + 0x5300;
	bx = device;
	cx = arg;
	dx = 0;
	
	asm ("outb %%al,%%dx ; movl %%eax,%0 ; movl $0,%%eax ; adc $0,%%eax"
	     : "=g" (ax), "=b" (bx), "=c" (cx), "=d" (dx), "=a" (carry)
	     : "d" (asmi_port), "a" (ax), "b" (bx), "c" (cx));

	if (aret)
		*aret = ax;
	if (bret)
		*bret = bx;
	if (cret)
		*cret = cx;
	if (carry)
		return (ax >> 8);
	return 0;
}

const char *const apm_error_table[] = {
  0,
  "Power Management functionality disabled",
  "Interface connection already in effect",
  "Interface not connected",
  "Real mode connection not established",
  "16-bit protected mode interface already established",
  "16-bit protected mode interface not supported",
  "32-bit protected mode interface already established",
  "32-bit protected mode interface not supported",
  "Unrecognized Device ID",
  "Parameter value in CX out of range",
  0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  "Can't enter requested state",
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, "No APM present",
                       0, 0, 0, 0, 0, 0, 0, 0, 0,
};

const char *apm_error(int err)
{
	const char *msg;
	static char buf[60];

	if (err > sizeof (apm_error_table) / sizeof (apm_error_table[0]))
		goto bad_value;
	msg = apm_error_table[err];
	if (msg)
		return msg;
bad_value:
	sprintf (buf, "Unknown APM error %x", err);
	return buf;
}

