From 11c8c6f3879baae4bad53ffb57b4f3a29bd7fe56 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 16 Apr 2004 16:34:44 +0000 Subject: [PATCH] missing file. --- src/mainboard/via/epia-m/smbus.inc | 542 +++++++++++++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 src/mainboard/via/epia-m/smbus.inc diff --git a/src/mainboard/via/epia-m/smbus.inc b/src/mainboard/via/epia-m/smbus.inc new file mode 100644 index 0000000000..99a1f7f77a --- /dev/null +++ b/src/mainboard/via/epia-m/smbus.inc @@ -0,0 +1,542 @@ +/* Useful macros PCIBUS, and SMBUS functions for getting DRAM going. */ +/* courtesy Eric Biederman of linuxnetworx.com */ + +#define CS_WRITE_BYTE(addr, byte) \ + movl $addr, %eax ; \ + movl $byte, %edx ; \ + PCI_WRITE_CONFIG_BYTE + +#define CS_WRITE_WORD(addr, word) \ + movl $addr, %eax ; \ + movl $word, %ecx ; \ + PCI_WRITE_CONFIG_WORD + +#define CS_WRITE_LONG(addr, dword) \ + movl $addr, %eax ; \ + movl $dword, %ecx ; \ + PCI_WRITE_CONFIG_DWORD + +#define DEVFN(device, function) (((device) << 3) + (function)) +#ifndef CONFIG_ADDR +#define CONFIG_ADDR(bus,devfn,where) (((bus) << 16) | ((devfn) << 8) | (where)) +#endif + +/* jump around these subrs */ +jmp smbus_pcibus_end + +/* generic SMB routines that work for many systems. The only one that might + * not work is the enable_smbus. + * you have to define PM_FUNCTION for this to work. + */#define SMBUS_IO_BASE 0xf00 +#define SMBHSTSTAT 0 +#define SMBHSTCTL 2 +#define SMBHSTCMD 3 +#define SMBHSTADD 4 +#define SMBHSTDAT0 5 +#define SMBHSTDAT1 6 +#define SMBBLKDAT 7 + +/* (DA) Lines added to get this to compile */ +#define PM_DEVFN CONFIG_ADDR(0,0x11*8,0) +#define DRAM_CONFIG_PORT 0x5a +#define SMBUS_MEM_DEVICE_0 0x50 +#define LAST_SMBUS_MEM_DEVICE SMBUS_MEM_DEVICE_0 +#define REGISTERED_DRAM_REGISTER $0x69 +#define REGISTERED_DRAM $0x2d +#define NONREGISTERED_DRAM $0 + + +enable_smbus: + /* put the SMBUS at port 0xf00 */ + CS_WRITE_WORD(PM_DEVFN+ 0xd0, SMBUS_IO_BASE|1) /* iobase addr */ + CS_WRITE_BYTE(PM_DEVFN + 0xd2, (0x4 << 1) | 1) /* smbus enable */ + CS_WRITE_WORD(PM_DEVFN + 0x4, 1) /* iospace enable */ + RET_LABEL(enable_smbus) + + /* + * Routine: setup_smbus + * Arguments: none + * Results: none + * Trashed: eax, edx + * Effects: The smbus is enabled + */ +setup_smbus: + xor %eax,%eax + movl $(SMBUS_IO_BASE +SMBHSTSTAT), %edx + outb %al, %dx + RET_LABEL(setup_smbus) + +#ifndef SMBUS_MEM_DEVICE_0 +#define SMBUS_MEM_DEVICE_0 (0xa << 3) +#endif +#define SMBUS_MEM_DEVICE_1 (SMBUS_MEM_DEVICE_0 +1) +#define SMBUS_MEM_DEVICE_2 (SMBUS_MEM_DEVICE_0 +2) +#define SMBUS_MEM_DEVICE_3 (SMBUS_MEM_DEVICE_0 +3) + + /* + * Routine: smbus_wait_until_ready + * Arguments: none + * Results: none + * Trashed: eax, edx + * Effects: Upon return the smbus is ready to accept commands + */ +smbus_wait_until_ready: + movl $(SMBUS_IO_BASE + SMBHSTSTAT), %edx +1: inb %dx, %al + testb $1, %al + jnz 1b + RET_LABEL(smbus_wait_until_ready) + + /* + * Routine: smbus_wait_until_done + * Arguments: none + * Results: none + * Trashed: eax, edx + * Effects: Upon return the smbus has completed it's most recent transation + */ +smbus_wait_until_done: + movl $(SMBUS_IO_BASE + SMBHSTSTAT), %edx +1: inb %dx, %al + testb $1, %al + jnz 1b +2: testb $0xFE, %al + jnz 3f + inb %dx, %al + testb $0xFE, %al + jz 2b +3: RET_LABEL(smbus_wait_until_done) + + + /* + * Routine: smbus_read_byte + * Arguments: %esp return address + * %bl device on the smbus to read from + * %bh address on the smbus to read + * + * Results: zf clear + * byte read %eax + * On Error: + * zf set + * %eax trashed + * + * Trashed: %edx, %eax + * Effects: reads a byte off of the smbus + */ + +#define SMBUS_READ_BYTE(device, address) \ + movl $( (device) | ((address) << 8)), %ebx ; \ + CALLSP(smbus_read_byte) + +smbus_read_byte: + /* poll until the smbus is ready for commands */ + CALL_LABEL(smbus_wait_until_ready) + + /* clear any lingering errors, so that the transaction will run */ + movl $(SMBUS_IO_BASE + SMBHSTSTAT), %edx + inb %dx, %al + outb %al, %dx + + /* set the device I'm talking to */ + movl $(SMBUS_IO_BASE + SMBHSTADD), %edx + movb %bl /* device */, %al + shlb $1, %al + orb $1, %al + outb %al, %dx + + /* set the command address... */ + movl $(SMBUS_IO_BASE + SMBHSTCMD), %edx + movb %bh /* address */, %al + outb %al, %dx + + /* clear the data byte */ + movl $(SMBUS_IO_BASE + SMBHSTDAT0), %edx + xorl %eax, %eax + outb %al, %dx + + /* start a byte read, with interrupts disabled */ + movl $(SMBUS_IO_BASE + SMBHSTCTL), %edx + movl $((0x2 << 2) | (1 << 6)), %eax + outb %al, %dx + + /* poll for transaction completion */ + CALL_LABEL(smbus_wait_until_done) + + /* read the results and see if we succeded */ + movl $(SMBUS_IO_BASE + SMBHSTSTAT), %edx + inb %dx, %al + testb $0x02, %al + jz 1f + movl $(SMBUS_IO_BASE + SMBHSTDAT0), %edx + inb %dx, %al +1: + RETSP + +#if 1 // TEMPORARY (DA) 20030718 + +/* now for code to actually do the deed. Eric did such a good job that + * this stuff is basically generic. + */ + /* + * Routine: spd_set_drb + * Arguments: None + * + * Trashed: %eax, %ebx, %ecx, %edx, %esi, %edi, %ebp, %esp, %eflags + * Effects: Uses serial presence detect to set the + * DRB registers which holds the ending memory address assigned + * to each DIMM. + * Notes: %ebp holds the currently detected end of memory. + * %ebx holds the configuration port & SMBUS_MEM_DEVICE for + * the current iteration through the loop. + * %edi holds the memory size for the first side of the DIMM. + * %esi holds the memory size for the second side of the DIMM. + * memory size is represent as a power of 2. + * An unset memory size is represented as -1 ie. 0xFFFFFFFF + */ + +spd_set_drb: + xorl %ebp, %ebp /* clear the memory address */ + movl $((DRAM_CONFIG_PORT << 16) |SMBUS_MEM_DEVICE_0), %ebx +spd_set_drb_loop_top: + // set -1 power-of-two for side 1 (called bank0 in most chipset docs) + xorl %edi, %edi + subl $1, %edi + // set -1 power-of-two for side 2 (called bank1 in most chipset docs) + xorl %esi, %esi + subl $1, %esi + + movb $3, %bh /* rows */ + CALLSP(smbus_read_byte) + // If it's zero, then we just set current %ebp into the row + // end register + jz 20f + andl $0xf, %eax + addl %eax, %edi + + movb $4, %bh /* columns */ + CALLSP(smbus_read_byte) + andl $0xf, %eax + addl %eax, %edi + + movb $17, %bh /* banks */ + CALLSP(smbus_read_byte) + andl $0xff, %eax + bsrl %eax, %ecx + addl %ecx, %edi + + /* Get the module data width and convert it to a power of two */ + movb $7, %bh /* (high byte) */ + CALLSP(smbus_read_byte) + andl $0xff, %eax + movl %eax, %ecx + shll $8, %ecx + + movb $6, %bh /* (low byte) */ + CALLSP(smbus_read_byte) + andl $0xff, %eax + orl %eax, %ecx + bsrl %ecx, %eax + addl %eax, %edi + + /* now I have the ram size in bits as a power of two (less 1) */ + // It is less 1 since we started with -1 above. + // OK, BITS as power of two (but minus 1) + // So, e.g., 8 MB is 64 Mb, 64 Mb is 26 bits. Subtract + // (26-1) or 25 + subl $25, %edi /* Make it multiples of 8MB */ + + /* side two */ + movb $5, %bh /* number of physical banks */ + CALLSP(smbus_read_byte) + cmp $1, %al + // it's only one bank + jbe 20f + // It's two banks. So assign edi to esi + /* for now only handle the symmetrical case */ + // it's two banks, assume however that they're the same size. + // it's stupid to have any other kind, right? + movl %edi, %esi +20: + /* Compute the end address for the DRB register */ + // If it is >= 8, i.e. >= 2^8 or 256, skip it. + // >= 8 is a bogus value. + cmpl $8, %edi + jae 21f + movl $1, %eax + movl %edi, %ecx + shll %cl, %eax + // increment row-end by the size of this DIMM half + addl %eax, %ebp +21: + /* Write the comuputed value for the first half of the DIMM */ + movl %ebp, %edx /* value to write into %edx */ + movl %ebx, %eax + shrl $16, %eax /* port address into %eax */ + PCI_WRITE_CONFIG_BYTE + + /* Compute the end address for the DRB register */ + cmpl $8, %esi + jae 30f + mov $1, %eax + movl %esi, %ecx + shll %cl, %eax + addl %eax, %ebp +30: + /* Write the comuputed value for the second half of the DIMM */ + movl %ebp, %edx /* value to write into %edx */ + movl %ebx, %eax + shrl $16, %eax /* port address into %eax */ + addl $1, %eax /* The second half uses one port high */ + PCI_WRITE_CONFIG_BYTE + + addl $0x00020001, %ebx /* increment the smbus device & the config port */ + cmpb $LAST_SMBUS_MEM_DEVICE, %bl /* see if I have reached the end */ + jbe spd_set_drb_loop_top + + /* o.k. I'm done return now */ + RET_LABEL(spd_set_drb) + + /* + * Routine: spd_set_dramc + * Arguments: None + * + * Trashed: %eax, %ebx, %edx, %esp, %eflags + * Effects: Uses serial presence detect to set the + * DRAMC register, which records if ram is registerd or not, + * and controls the refresh rate. + * The refresh rate is not set here, as memory refresh + * cannot be enbaled until after memory is initialized. + * see spd_enable_refresh. + * Notes: + * FIXME: Check for illegal/unsupported ram configurations and abort + * FIXME: won't work with non-contiguous DRAM size regs (like VIA) + * need an indirect pointer to an array of reg #s + */ + +spd_set_dramc: + /* auto detect if ram is registered or not. */ + /* The DRAMC register also contorls the refresh rate but we can't + * set that here because we must leave refresh disabled. + * see: spd_enable_refresh + */ + /* Find the first dimm and assume the rest are the same */ + /* Load the smbus device and port int %ebx */ + movl $((21 << 8) | SMBUS_MEM_DEVICE_0), %ebx +1: CALLSP(smbus_read_byte) + jz 2f + andl $0x12, %eax + jmp spd_set_dramc_out + +2: addl $1, %ebx /* increment the device */ + cmpb $LAST_SMBUS_MEM_DEVICE, %bl + jbe 1b + /* We couldn't find anything we must have no memory */ + jmp no_memory + +spd_set_dramc_out: + testb $0x12, %al + jz 2f + movl REGISTERED_DRAM, %eax + jmp 1f +2: movl NONREGISTERED_DRAM, %eax +1: movl %eax, %edx + movl REGISTERED_DRAM_REGISTER, %eax + PCI_WRITE_CONFIG_BYTE + RET_LABEL(spd_set_dramc) + + + + /* + * Routine: spd_read_refresh + * Arguments: None + * + * Trashed: %eax, %ebx, %ecx, %edx, %esp, %eflags + * Effects: Uses serial presence detect to find refresh rates. + * returns the rate in %eax + * It's up to you to set hardware up. + * FIXME: Check for illegal/unsupported ram configurations and abort + */ + + +spd_read_refresh: + /* Find the first dimm and assume the rest are the same */ + /* Load the smbus device and port int %ebx */ + movl $((12 << 8) | SMBUS_MEM_DEVICE_0), %ebx +1: CALLSP(smbus_read_byte) + jz 2f + andl $0x7f, %eax + jmp spd_enable_refresh_out + +2: addl $1, %ebx /* increment the device */ + cmpb $LAST_SMBUS_MEM_DEVICE, %bl + jbe 1b + /* We couldn't find anything we must have no memory */ + xorl %eax, %eax +spd_enable_refresh_out: + RET_LABEL(spd_enable_refresh) + + /* + * Routine: spd_set_rps + * Arguments: None + * + * Trashed: %eax, %ebx, %ecx, %edx, %esi, %edi, %esp, %eflags + * Effects: Uses serial presence detect to set the row size + * on a given DIMM + * Notes: %esi accumulates the row sizes of all of the DIMMs + * %ecx holds the current bit into into %esi + * %bl holds the current SMBUS device + * FIXME: Check for illegal/unsupported ram configurations and abort + */ + +spd_set_rps: + /* The RPS register holds the size of a ``page'' of DRAM on each DIMM */ + /* default all page sizes to 2KB */ + xorl %esi, %esi + /* Index into %esi of bit to set */ + movl $0 , %ecx + /* Load the smbus device into %ebx */ + movl $SMBUS_MEM_DEVICE_0, %ebx + +1: movb $3, %bh + CALLSP(smbus_read_byte) /* row address bits */ + jz 2f + andl $0xf, %eax + movl %eax, %edi + /* I now have the row page size as a power of 2 */ + subl $11, %edi /* Now make it in multiples of 2Kb */ + jbe 2f + /* FIXME: do something with page sizes greather than 8KB!! */ + shll %cl, %edi + orl %edi, %esi + /* side two */ + movb $5, %bh + CALLSP(smbus_read_byte) /* number of physical banks */ + cmp $1, %al + jbe 2f + /* for now only handle the symmtrical case */ + shll $2, %edi + shll %cl, %edi + orl %edi, %esi + +2: addl $1, %ebx /* increment the device */ + addl $4, %ecx /* increment the shift count */ + cmpb $LAST_SMBUS_MEM_DEVICE, %bl + jbe 1b + + movl $0x7f, %eax +/* I'm not sure what we should do here. Do nothing. */ +/* PCI_WRITE_CONFIG_WORD*/ + RET_LABEL(spd_set_rps) + + /* + * Routine: spd_set_pgpol + * Arguments: None + * + * Trashed: %eax, %ebx, %ecx, %edx, %esi, %esp, %eflags + * Effects: Uses serial presence detect to set the number of banks + * on a given DIMM + * Notes: %esi accumulates the banks sizes of all of the DIMMs + * %ecx holds the current bit into into %esi + * %bl holds the current SMBUS device + * FIXME: Check for illegal/unsupported ram configurations and abort + */ + +spd_set_pgpol: + /* The PGPOL register stores the number of logical banks per DIMM, + * and number of clocks the DRAM controller waits in the idle + * state. + */ + /* default all bank counts 2 */ + xorl %esi, %esi + /* Index into %esi of bit to set */ + movl $0 , %ecx + /* Load the smbus device into %ebx */ + movl $SMBUS_MEM_DEVICE_0, %ebx + +1: movb $17, %bh + CALLSP(smbus_read_byte) /* logical banks */ + jz 2f + cmp $0x4, %eax + jl 2f + movl $0x1, %eax + shll %cl, %eax + orl %eax, %esi + /* side two */ + movb $5, %bh + CALLSP(smbus_read_byte) /* number of physical banks */ + cmp $1, %al + jbe 2f + /* for now only handle the symmtrical case */ + movl $0x2, %eax + shll %cl, %eax + orl %eax, %esi + +2: addl $1, %ebx /* increment the device */ + addl $2, %ecx /* increment the shift count */ + cmpb $LAST_SMBUS_MEM_DEVICE, %bl + jbe 1b + + shll $8, %esi + orl $0x7, %esi /* 32 clocks idle time */ + movl %esi, %ecx + movl $0x78, %eax +/* I'm unclear on the concept for non-intel devices */ +/* PCI_WRITE_CONFIG_WORD*/ + RET_LABEL(spd_set_pgpol) + + + + /* + * Routine: spd_enable_nbxcfg + * Arguments: None + * + * Trashed: %eax, %ebx, %ecx, %edx, %esi, %esp, %eflags + * Effects: Uses serial presence detect to set the + * ECC support flags in the NBXCFG register + * Notes: %esi accumulates the ECC support of the individual DIMMs. + * %ecx holds the bit that should be flipped for the current DIMM. + * %bl holds the smbus device that corresponds to the current DIMM. + * FIXME: Check for illegal/unsupported ram configurations and abort + */ + +spd_set_nbxcfg: + /* say all dimms have no ECC support */ + movl $0xFF, %esi + /* Index into %esi of bit to set */ + movl $0 , %ecx + /* Load the smbus device into %ebx */ + movl $SMBUS_MEM_DEVICE_0, %ebx + +1: movb $11, %bh + CALLSP(smbus_read_byte) /* module error correction type */ + jz 2f + cmp $0x2, %eax /* 0 == None, 1 == Parity, 2 == ECC */ + jne 2f + movl $0x1, %eax + shll %cl, %eax + xorl %eax, %esi + + /* side two */ + movb $5, %bh + CALLSP(smbus_read_byte) /* number of physical banks */ + cmp $1, %al + jbe 2f + /* The only is the symmtrical case */ + movl $0x2, %eax + shll %cl, %eax + xorl %eax, %esi + +2: addl $1, %ebx /* increment the device */ + addl $2, %ecx /* increment the shift count */ + cmpb $LAST_SMBUS_MEM_DEVICE, %bl + jbe 1b + + movl %esi, %edx +/* at some point, we need to indicate how to turn ECC on. Not yet. + movl $0x53, %eax + PCI_WRITE_CONFIG_BYTE + */ + RET_LABEL(spd_set_nbxcfg) + +#endif + +smbus_pcibus_end: