/* * This file is part of the coreboot project. * * Copyright (C) 2011 Alexandru Gagniuc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include "early_vx900.h" #include #include #include /** * \brief SMBUS IO ports in relation to the base IO port */ #define SMBHSTSTAT(base) (u16)(u32)base + 0x0 #define SMBSLVSTAT(base) (u16)(u32)base + 0x1 #define SMBHSTCTL(base) (u16)(u32)base + 0x2 #define SMBHSTCMD(base) (u16)(u32)base + 0x3 #define SMBXMITADD(base) (u16)(u32)base + 0x4 #define SMBHSTDAT0(base) (u16)(u32)base + 0x5 #define SMBHSTDAT1(base) (u16)(u32)base + 0x6 #define SMBBLKDAT(base) (u16)(u32)base + 0x7 #define SMBSLVCTL(base) (u16)(u32)base + 0x8 #define SMBTRNSADD(base) (u16)(u32)base + 0x9 #define SMBSLVDATA (base) (u16)(u32)base + 0xa static void smbus_delays(int delays) { while (delays--) smbus_delay(); } /** * Read a byte from the SMBus. * * @param smbus_dev The PCI address of the SMBus device . * @param addr The address location of the DIMM on the SMBus. * @param offset The offset the data is located at. */ u8 smbus_read_byte(u32 smbus_dev, u8 addr, u8 offset) { u8 val; /* Initialize SMBUS sequence */ smbus_reset(smbus_dev); /* Clear host data port. */ outb(0x00, SMBHSTDAT0(smbus_dev)); smbus_wait_until_ready(smbus_dev); smbus_delays(50); /* Actual addr to reg format. */ addr = (addr << 1); addr |= 1; /* read command */ outb(addr, SMBXMITADD(smbus_dev)); outb(offset, SMBHSTCMD(smbus_dev)); /* Start transaction, byte data read. */ outb(0x48, SMBHSTCTL(smbus_dev)); smbus_wait_until_ready(smbus_dev); val = inb(SMBHSTDAT0(smbus_dev)); return val; } void enable_smbus(void) { pci_devfn_t dev; u8 reg8; u32 smbus_dev = (u32) SMBUS_IO_BASE; /* Locate the Power Management control */ dev = pci_locate_device(PCI_ID(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX900_LPC), 0); if (dev == PCI_DEV_INVALID) { die("Power Management Controller not found\n"); } /* * To use SMBus to manage devices on the system board, it is a must to * enable SMBus function by setting * PMU_RXD2[0] (SMBus Controller Enable) to 1. * And set PMU_RXD0 and PMU_RXD1 (SMBus I/O Base) to an appropriate * I/O port address, so that all registers in SMBus I/O port can be * accessed. */ reg8 = pci_read_config8(dev, 0xd2); /* Enable SMBus controller */ reg8 |= 1; /* Set SMBUS clock from 128k source */ reg8 |= 1 << 2; pci_write_config8(dev, 0xd2, reg8); reg8 = pci_read_config8(dev, 0x94); /* SMBUS clock from divider of 14.318 MHz */ reg8 &= ~(1 << 7); pci_write_config8(dev, 0x94, reg8); /* Set SMBus IO base */ pci_write_config16(dev, 0xd0, SMBUS_IO_BASE); /* * Initialize the SMBus sequence: */ /* Clear SMBus host status register */ smbus_reset(smbus_dev); /* Clear SMBus host data 0 register */ outb(0x00, SMBHSTDAT0(smbus_dev)); /* Wait for SMBUS */ smbus_wait_until_ready(smbus_dev); } static int spd_get_length(u8 spd_byte0) { spd_byte0 &= 0xf; switch (spd_byte0) { case 0x3: return 256; case 0x2: return 176; case 0x1: return 128; default: break; } return 0; } void spd_read(u8 addr, spd_raw_data spd) { u8 reg; int i, regs; u32 smbus_dev = SMBUS_IO_BASE; reg = smbus_read_byte(smbus_dev, addr, 2); if (reg != 0x0b) { printk(BIOS_DEBUG, "SMBUS device %x not a DDR3 module\n", addr); spd[2] = 0; return; } reg = smbus_read_byte(smbus_dev, addr, 0); if ((regs = spd_get_length(reg)) == 0) { printk(BIOS_INFO, "No DIMM present at %x\n", addr); spd[2] = 0; return; } for (i = 0; i < regs; i++) spd[i] = smbus_read_byte(smbus_dev, addr, i); } void dump_spd_data(spd_raw_data spd) { int len, i; u8 reg; if ((len = spd_get_length(spd[0])) == 0) { printk(BIOS_DEBUG, "Invalid SPD\n"); return; } /* * I originally saw this way to present SPD data in code from VIA. I * really liked the idea, so here it goes. */ printk(BIOS_DEBUG, " 00 01 02 03 04 05 06 07 07 09 0A 0B 0C 0D 0E 0F\n"); printk(BIOS_DEBUG, "---+------------------------------------------------"); for (i = 0; i < len; i++) { reg = spd[i]; if ((i & 0x0f) == 0) printk(BIOS_DEBUG, "\n%.2x |", i); printk(BIOS_DEBUG, " %.2x", reg); } printk(BIOS_DEBUG, "\n"); }