blob: 93a9254453c51c9d5e8e58e0b911007f9d670ee1 [file] [log] [blame]
/*
* This file is part of the coreboot project.
*
* Copyright (C) 2011 Alexandru Gagniuc <mr.nuke.me@gmail.com>
*
* 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 <device/pci_ids.h>
#include "early_vx900.h"
#include <device/early_smbus.h>
#include <arch/io.h>
#include <console/console.h>
/**
* \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)
{
device_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");
}