blob: f395043764ae8520ac61023c7822a1cacd6da9b1 [file] [log] [blame]
/* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* DIMM Serial Presence Detect interface normal access is via I2C but some
* platforms hide SPD behind memory controller and other access method must be
* used.
*/
#define _LARGEFILE64_SOURCE
#include <arpa/inet.h> /* ntohl() */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include "mosys/alloc.h"
#include "mosys/list.h"
#include "mosys/log.h"
#include "mosys/platform.h"
#include "intf/i2c.h"
#include "lib/cbfs_core.h"
#include "lib/smbios.h"
#include "lib/smbios_tables.h"
#include "lib/spd.h"
static spd_raw_override spd_raw_access_override;
const char *ddr_freq_prettyprint[] = {
[DDR_FREQ_UNKNOWN] = "Unknown",
[DDR_333] = "667",
[DDR_400] = "800",
[DDR_533] = "1066",
[DDR_667] = "1333",
[DDR_800] = "1600",
[DDR_933] = "1866",
[DDR_1067] = "2133",
[DDR_1400] = "2400",
};
/*
* spd_raw_i2c - Read/write to/from SPD via I2C
*
* @intf: platform interface
* @bus: bus to read from
* @address: address on bus
* @reg: configuration register
* @length: number of bytes to read
* @data: data buffer
* @rw: 1=write, 0=read
*
* returns number of bytes written or read
* returns <0 to indicate error
*/
static int spd_raw_i2c(struct platform_intf *intf, int bus,
int address, int reg, int length, void *data, int rw)
{
if (!intf->op->i2c) {
return -ENOSYS;
}
switch (rw) {
case SPD_READ:
return intf->op->i2c->smbus_read_reg(intf, bus, address,
reg, length, data);
case SPD_WRITE:
return intf->op->i2c->smbus_write_reg(intf, bus, address,
reg, length, data);
}
return -1;
}
/* spd_raw_access - read/write access method to SPDs
*
* @intf: platform interface
* @bus: SMBus number of requested SPD
* @address: SMBus address of requested SPD
* @reg: register in SPD to perform access on
* @length: length of access to perform
* @data: pointer to buffer that is either filled or read from to do the access
* @rw: specify SPD operation (SPD_READ or SPD_WRITE)
*
* returns 0 on success, < 0 on error.
*/
int spd_raw_access(struct platform_intf *intf, int bus, int address,
int reg, int length, void *data, int rw)
{
if (spd_raw_access_override != NULL) {
return spd_raw_access_override(intf, bus, address,
reg, length, data, rw);
}
return spd_raw_i2c(intf, bus, address, reg, length, data, rw);
}
int spd_read_i2c(struct platform_intf *intf, int bus,
int address, int reg, int length, void *data)
{
const char module[] = "eeprom";
if (!intf->cb->memory || !intf->cb->memory->dimm_map)
return -1;
/* Read info from /sys */
if (intf->op->i2c->find_driver(intf, module)) {
FILE *fp;
int ret;
char path[80];
/* Get the data */
snprintf(path, sizeof(path), "%s/%u-%04x/%s",
intf->op->i2c->sys_root, bus, address, module);
if (!(fp = fopen(path, "r"))) {
lprintf(LOG_DEBUG,
"Failed to open %s\n", path);
return -1;
}
fseek(fp, reg, SEEK_SET);
ret = fread(data, 1, length, fp);
fclose(fp);
return ret;
} else {
return spd_raw_access(intf, bus, address, reg,
length, data, SPD_READ);
}
return -1;
}
#if 0
int spd_write_i2c(struct platform_intf *intf,
int dimm, int reg, int length, const void *data)
{
int bus, address;
if (!intf->cb->memory->dimm_map)
return -1;
/* convert logical dimm map */
bus = intf->cb->memory->dimm_map(intf, DIMM_TO_BUS, dimm);
if (bus < 0)
return -1;
address = intf->cb->memory->dimm_map(intf, DIMM_TO_ADDRESS, dimm);
if (address < 0)
return -1;
//FIXME: we cast data to remove the const - can we do better?
return intf->cb->memory->spd->write(intf, bus, address, reg,
length, (void *)data);
}
#endif
int override_spd_raw_access(spd_raw_override override)
{
spd_raw_access_override = override;
return 0;
}
/* new_spd_device() - create a new instance of spd_device
*
* @intf: platform_intf for access
* @dimm: Google logical dimm number to represent
*
* returns allocated and filled in spd_devices on success, NULL if error
*/
struct spd_device *new_spd_device(struct platform_intf *intf, int dimm)
{
struct spd_device *spd;
if (intf == NULL || dimm < 0) {
return NULL;
}
spd = mosys_malloc(sizeof(*spd));
spd->dimm_num = dimm;
memset(&spd->eeprom.data[0], 0xff, SPD_MAX_LENGTH);
if (intf->cb->memory->spd->read(intf, dimm, 0, 3,
&spd->eeprom.data[0]) != 3) {
free(spd);
return NULL;
}
spd->dram_type = (enum spd_dram_type)spd->eeprom.data[2];
spd->eeprom.length = spd_total_size(&spd->eeprom.data[0]);
/* Invalid length. */
if (spd->eeprom.length <= 0) {
lperror(LOG_DEBUG, "Invalid DIMM(%d) SPD length(%d).\n",
dimm, spd->eeprom.length);
free(spd);
return NULL;
}
/* Fill in copy of SPD eeprom area. */
if (intf->cb->memory->spd->read(intf, dimm, 0,
spd->eeprom.length,
&spd->eeprom.data[0])
!= spd->eeprom.length) {
lperror(LOG_DEBUG,
"Unable to read full contents of SPD from DIMM %d.\n",
dimm);
free(spd);
return NULL;
}
return spd;
}
static int find_spd_by_part_number(struct platform_intf *intf, int dimm,
uint8_t *spd, uint32_t num_spd)
{
char *smbios_part_num;
uint8_t i;
uint8_t *ptr;
struct smbios_table table;
lprintf(LOG_DEBUG, "Use SMBIOS type 17 to get memory information\n");
if (smbios_find_table(intf, SMBIOS_TYPE_MEMORY, dimm, &table,
SMBIOS_LEGACY_ENTRY_BASE,
SMBIOS_LEGACY_ENTRY_LEN) < 0) {
lprintf(LOG_DEBUG, "Can't find smbios type17\n");
return -1;
}
smbios_part_num = table.string[table.data.mem_device.part_number];
for (i = 0; i < num_spd; i++) {
ptr = (spd + i * 256);
if (!memcmp(smbios_part_num, ptr + 128, 18)) {
lprintf(LOG_DEBUG, "found %x\n", i);
return i;
}
}
return -1;
}
int spd_read_from_cbfs(struct platform_intf *intf,
int module, int reg, int num_bytes_to_read,
uint8_t *spd, size_t fw_size, uint8_t *fw)
{
struct cbfs_file *file;
int spd_index = 0;
uint32_t spd_offset, num_spd;
uint8_t *ptr;
int ret = 0;
if ((file = cbfs_find("spd.bin", fw, fw_size)) == NULL) {
ret = -1;
goto out;
}
ptr = (uint8_t *)file + ntohl(file->offset);
num_spd = ntohl(file->len) / 256;
spd_index = find_spd_by_part_number(intf, module, ptr, num_spd);
if (spd_index < 0) {
ret = -1;
goto out;
}
MOSYS_CHECK((spd_index * 256) + reg + num_bytes_to_read <=
num_spd * 256);
spd_offset = ntohl(file->offset) + (spd_index * 256);
lprintf(LOG_DEBUG, "Using memory config %u\n", spd_index);
memcpy(spd, (void *)file + spd_offset + reg, num_bytes_to_read);
ret = num_bytes_to_read;
out:
return ret;
}