#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"

/*
 *	SMBus support for the PIIX4
 */
enum
{
	IntelVendID=	0x8086,
	Piix4PMID=	0x7113,		/* PIIX4 power management function */

	/* SMBus configuration registers (function 3) */
	SMBbase=	0x90, /* 4 byte base address (bit 0 == 1, bit 3:1 == 0) */
	SMBconfig=	0xd2,
	SMBintrselect=	(7<<1),
	SMIenable=	(0<<1),		/* interrupts sent to SMI# */
	IRQ9enable=	(4<<1),		/* interrupts sent to IRQ9 */
	SMBenable=	(1<<0),		/* 1 enables */

	/* SMBus IO space registers */
	Hoststatus=	0x0,	/* (writing 1 bits reset the interrupt bits) */
	Failed=		(1<<4),		/* transaction terminated by KILL */
	Bus_error=	(1<<3),		/* transaction collision */
	Dev_error=	(1<<2),		/* device error interrupt */
	Host_complete=	(1<<1),		/* host command completion interrupt */
	Host_busy=	(1<<0),		/*  */
	Slavestatus=	0x1,	/* (writing 1 bits reset) */
	Alert_sts=	(1<<5),		/* someone asserted SMBALERT# */
	Shdw2_sts=	(1<<4),		/* slave accessed shadow 2 port */
	Shdw1_sts=	(1<<3),		/* slave accessed shadow 1 port */
	Slv_sts=	(1<<2),		/* slave accessed shadow 1 port */
	Slv_bsy=	(1<<0),
	Hostcontrol=	0x2,
	Start=		(1<<6),		/* start execution */
	Cmd_prot=	(7<<2),		/* command protocol mask */
	Quick=		(0<<2),		/*  address only */
	Byte=		(1<<2),		/*  address + cmd */
	ByteData=	(2<<2),		/*  address + cmd + data */
	WordData=	(3<<2),		/*  address + cmd + data + data */
	Kill=		(1<<1),		/* abort in progress command */
	Ienable=	(1<<0),		/* enable completion interrupts */
	Hostcommand=	0x3,
	Hostaddress=	0x4,
	AddressMask=	(0x7f<<1),	/* target address */
	Read=		(1<<0),		/* 1 == read, 0 == write */
	Hostdata0=	0x5,
	Hostdata1=	0x6,
	Blockdata=	0x7,
	Slavecontrol=	0x8,
	Alert_en=	(1<<3),	/* enable inter on SMBALERT# */
	Shdw2_en=	(1<<2),	/* enable inter on external shadow 2 access */
	Shdw1_en=	(1<<1),	/* enable inter on external shadow 1 access */
	Slv_en=		(1<<0),	/* enable inter on access of host ctlr slave port */
	Shadowcommand=	0x9,
	Slaveevent=	0xa,
	Slavedata=	0xc,
};

static struct
{
	int	rw;
	int	cmd;
	int	len;
	int	proto;
} proto[] =
{
	[SMBquick]	{ 0,	0,	0,	Quick },
	[SMBsend]	{ 0,	1,	0,	Byte },
	[SMBbytewrite]	{ 0,	1,	1,	ByteData },
	[SMBwordwrite]	{ 0,	1,	2,	WordData },
	[SMBrecv]	{ Read,	0,	1, 	Byte },
	[SMBbyteread]	{ Read,	1,	1,	ByteData },
	[SMBwordread]	{ Read,	1,	2,	WordData },
};

static void
transact(SMBus *s, int type, int addr, int cmd, uchar *data)
{
	int tries, status;
	char err[256];

	if(type < 0 || type > nelem(proto))
		panic("piix4smbus: illegal transaction type %d", type);

	if(waserror()){
		qunlock(s);
		nexterror();
	}
	qlock(s);

	/* wait a while for the host interface to be available */
	for(tries = 0; tries < 1000000; tries++){
		if((inb(s->base+Hoststatus) & Host_busy) == 0)
			break;
		sched();
	}
	if(tries >= 1000000){
		/* try aborting current transaction */
		outb(s->base+Hostcontrol, Kill);
		for(tries = 0; tries < 1000000; tries++){
			if((inb(s->base+Hoststatus) & Host_busy) == 0)
				break;
			sched();
		}
		if(tries >= 1000000){
			snprint(err, sizeof(err), "SMBus jammed: %2.2ux", inb(s->base+Hoststatus));
			error(err);
		}
	}

	/* set up for transaction */
	outb(s->base+Hostaddress, (addr<<1)|proto[type].rw);
	if(proto[type].cmd)
		outb(s->base+Hostcommand, cmd);
	if(proto[type].rw != Read){
		switch(proto[type].len){
		case 2:
			outb(s->base+Hostdata1, data[1]);
			/* fall through */
		case 1:
			outb(s->base+Hostdata0, data[0]);
			break;
		}
	}


	/* reset the completion/error bits and start transaction */
	outb(s->base+Hoststatus, Failed|Bus_error|Dev_error|Host_complete);
	outb(s->base+Hostcontrol, Start|proto[type].proto);

	/* wait for completion */
	status = 0;
	for(tries = 0; tries < 1000000; tries++){
		status = inb(s->base+Hoststatus);
		if(status & (Failed|Bus_error|Dev_error|Host_complete))
			break;
		sched();
	}
	if((status & Host_complete) == 0){
		snprint(err, sizeof(err), "SMBus request failed: %2.2ux", status);
		error(err);
	}

	/* get results */
	if(proto[type].rw == Read){
		switch(proto[type].len){
		case 2:
			data[1] = inb(s->base+Hostdata1);
			/* fall through */
		case 1:
			data[0] = inb(s->base+Hostdata0);
			break;
		}
	}
	qunlock(s);
	poperror();
}

static SMBus smbusproto =
{
	.transact = transact,
};

/*
 *  return 0 if this is a piix4 with an smbus interface
 */
SMBus*
piix4smbus(void)
{
	Pcidev *p;
	static SMBus *s;

	if(s != nil)
		return s;

	p = pcimatch(nil, IntelVendID, Piix4PMID);
	if(p == nil)
		return nil;

	s = smalloc(sizeof(*s));
	memmove(s, &smbusproto, sizeof(*s));
	s->arg = p;

	/* disable the smbus */
	pcicfgw8(p, SMBconfig, IRQ9enable|0);

	/* see if bios gave us a viable port space */
	s->base = pcicfgr32(p, SMBbase) & ~1;
print("SMB base from bios is 0x%lux\n", s->base);
	if(ioalloc(s->base, 0xd, 0, "piix4smbus") < 0){
		s->base = ioalloc(-1, 0xd, 2, "piix4smbus");
		if(s->base < 0){
			free(s);
			print("piix4smbus: can't allocate io port\n");
			return nil;
		}
print("SMB base ialloc is 0x%lux\n", s->base);
		pcicfgw32(p, SMBbase, s->base|1);
	}

	/* disable SMBus interrupts, abort any transaction in progress */
	outb(s->base+Hostcontrol, Kill);
	outb(s->base+Slavecontrol, 0);

	/* enable the smbus */
	pcicfgw8(p, SMBconfig, IRQ9enable|SMBenable);

	return s;
}
