plan9fox/sys/src/9/pc/piix4smbus.c
2011-12-12 16:55:26 +01:00

216 lines
5.3 KiB
C

#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 = malloc(sizeof(*s));
if(s == nil)
panic("piix4smbus: no memory for SMBus");
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;
}