Merge pull request #541 from antmicro/jboc/spd-read

Add support for I2C to read SPD EEPROM
This commit is contained in:
enjoy-digital 2020-05-27 19:03:50 +02:00 committed by GitHub
commit 3fd6ecd86e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 414 additions and 7 deletions

View File

@ -17,6 +17,7 @@ OBJECTS = isr.o \
cmd_bios.o \
cmd_mem.o \
cmd_boot.o \
cmd_i2c.o \
cmd_spiflash.o \
cmd_litedram.o \
cmd_liteeth.o \

View File

@ -0,0 +1,121 @@
// SPDX-License-Identifier: BSD-Source-Code
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <generated/csr.h>
#include <i2c.h>
#include "../command.h"
#include "../helpers.h"
/**
* Command "i2creset"
*
* Reset I2C line state in case a slave locks the line.
*
*/
#ifdef CSR_I2C_BASE
define_command(i2creset, i2c_reset, "Reset I2C line state", I2C_CMDS);
#endif
/**
* Command "i2cwr"
*
* Write I2C slave memory using 7-bit slave address and 8-bit memory address.
*
*/
#ifdef CSR_I2C_BASE
static void i2cwr_handler(int nb_params, char **params)
{
int i;
char *c;
unsigned char write_params[32]; // also indirectly limited by CMD_LINE_BUFFER_SIZE
if (nb_params < 2) {
printf("i2cwr <slaveaddr7bit> <addr> [<data>, ...]");
return;
}
if (nb_params - 1 > sizeof(write_params)) {
printf("Max data length is %d", sizeof(write_params));
return;
}
for (i = 0; i < nb_params; ++i) {
write_params[i] = strtoul(params[i], &c, 0);
if (*c != 0) {
printf("Incorrect value of parameter %d", i);
return;
}
}
if (!i2c_write(write_params[0], write_params[1], &write_params[2], nb_params - 2)) {
printf("Error during I2C write");
return;
}
}
define_command(i2cwr, i2cwr_handler, "Write over I2C", I2C_CMDS);
#endif
/**
* Command "i2crd"
*
* Read I2C slave memory using 7-bit slave address and 8-bit memory address.
*
*/
#ifdef CSR_I2C_BASE
static void i2crd_handler(int nb_params, char **params)
{
char *c;
int len;
unsigned char slave_addr, addr;
unsigned char buf[256];
bool send_stop = true;
if (nb_params < 3) {
printf("i2crd <slaveaddr7bit> <addr> <len> [<send_stop>]");
return;
}
slave_addr = strtoul(params[0], &c, 0);
if (*c != 0) {
printf("Incorrect slave address");
return;
}
addr = strtoul(params[1], &c, 0);
if (*c != 0) {
printf("Incorrect memory address");
return;
}
len = strtoul(params[2], &c, 0);
if (*c != 0) {
printf("Incorrect data length");
return;
}
if (len > sizeof(buf)) {
printf("Max data count is %d", sizeof(buf));
return;
}
if (nb_params > 3) {
send_stop = strtoul(params[3], &c, 0) != 0;
if (*c != 0) {
printf("Incorrect send_stop value");
return;
}
}
if (!i2c_read(slave_addr, addr, buf, len, send_stop)) {
printf("Error during I2C read");
return;
}
dump_bytes((unsigned int *) buf, len, addr);
}
define_command(i2crd, i2crd_handler, "Read over I2C", I2C_CMDS);
#endif

View File

@ -2,8 +2,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <generated/csr.h>
#include <i2c.h>
#include <liblitedram/sdram.h>
@ -221,3 +223,55 @@ define_command(sdrlevel, sdrlevel, "Perform read/write leveling", LITEDRAM_CMDS)
#ifdef CSR_SDRAM_BASE
define_command(memtest, memtest, "Run a memory test", LITEDRAM_CMDS);
#endif
/**
* Command "spdread"
*
* Read contents of SPD EEPROM memory.
* SPD address is a 3-bit address defined by the pins A0, A1, A2.
*
*/
#ifdef CSR_I2C_BASE
#define SPD_RW_PREAMBLE 0b1010
#define SPD_RW_ADDR(a210) ((SPD_RW_PREAMBLE << 3) | ((a210) & 0b111))
static void spdread_handler(int nb_params, char **params)
{
char *c;
unsigned char spdaddr;
unsigned char buf[256];
int len = sizeof(buf);
bool send_stop = true;
if (nb_params < 1) {
printf("spdread <spdaddr> [<send_stop>]");
return;
}
spdaddr = strtoul(params[0], &c, 0);
if (*c != 0) {
printf("Incorrect address");
return;
}
if (spdaddr > 0b111) {
printf("SPD EEPROM max address is 0b111 (defined by A0, A1, A2 pins)");
return;
}
if (nb_params > 1) {
send_stop = strtoul(params[1], &c, 0) != 0;
if (*c != 0) {
printf("Incorrect send_stop value");
return;
}
}
if (!i2c_read(SPD_RW_ADDR(spdaddr), 0, buf, len, send_stop)) {
printf("Error when reading SPD EEPROM");
return;
}
dump_bytes((unsigned int *) buf, len, 0);
}
define_command(spdread, spdread_handler, "Read SPD EEPROM", LITEDRAM_CMDS);
#endif

View File

@ -15,6 +15,7 @@
#define MEM_CMDS 3
#define BOOT_CMDS 3
#define SPIFLASH_CMDS 4
#define I2C_CMDS 4
#define LITEDRAM_CMDS 4
#define LITEETH_CMDS 5
#define LITESDCARD_CMDS 7

View File

@ -0,0 +1,26 @@
#ifndef __I2C_H
#define __I2C_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
/* I2C frequency defaults to a safe value in range 10-100 kHz to be compatible with SMBus */
#ifndef I2C_FREQ_HZ
#define I2C_FREQ_HZ 50000
#endif
#define I2C_ADDR_WR(addr) ((addr) << 1)
#define I2C_ADDR_RD(addr) (((addr) << 1) | 1u)
void i2c_reset(void);
bool i2c_write(unsigned char slave_addr, unsigned char addr, const unsigned char *data, unsigned int len);
bool i2c_read(unsigned char slave_addr, unsigned char addr, unsigned char *data, unsigned int len, bool send_stop);
#ifdef __cplusplus
}
#endif
#endif /* __I2C_H */

View File

@ -2,7 +2,7 @@ include ../include/generated/variables.mak
include $(SOC_DIRECTORY)/software/common.mak
OBJECTS = exception.o libc.o errno.o crc16.o crc32.o console.o \
system.o id.o uart.o time.o qsort.o strtod.o spiflash.o strcasecmp.o
system.o id.o uart.o time.o qsort.o strtod.o spiflash.o strcasecmp.o i2c.o
all: crt0-ctr.o crt0-xip.o libbase.a libbase-nofloat.a

View File

@ -0,0 +1,204 @@
// This file is Copyright (c) 2020 Antmicro <www.antmicro.com>
#include <i2c.h>
#include <generated/csr.h>
#ifdef CSR_I2C_BASE
#define I2C_PERIOD_CYCLES (CONFIG_CLOCK_FREQUENCY / I2C_FREQ_HZ)
#define I2C_DELAY(n) cdelay((n)*I2C_PERIOD_CYCLES/4)
static inline void cdelay(int i)
{
while(i > 0) {
__asm__ volatile(CONFIG_CPU_NOP);
i--;
}
}
static inline void i2c_oe_scl_sda(bool oe, bool scl, bool sda)
{
i2c_w_write(
((oe & 1) << CSR_I2C_W_OE_OFFSET) |
((scl & 1) << CSR_I2C_W_SCL_OFFSET) |
((sda & 1) << CSR_I2C_W_SDA_OFFSET)
);
}
// START condition: 1-to-0 transition of SDA when SCL is 1
static void i2c_start(void)
{
i2c_oe_scl_sda(1, 1, 1);
I2C_DELAY(1);
i2c_oe_scl_sda(1, 1, 0);
I2C_DELAY(1);
i2c_oe_scl_sda(1, 0, 0);
I2C_DELAY(1);
}
// STOP condition: 0-to-1 transition of SDA when SCL is 1
static void i2c_stop(void)
{
i2c_oe_scl_sda(1, 0, 0);
I2C_DELAY(1);
i2c_oe_scl_sda(1, 1, 0);
I2C_DELAY(1);
i2c_oe_scl_sda(1, 1, 1);
I2C_DELAY(1);
i2c_oe_scl_sda(0, 1, 1);
}
// Call when in the middle of SCL low, advances one clk period
static void i2c_transmit_bit(int value)
{
i2c_oe_scl_sda(1, 0, value);
I2C_DELAY(1);
i2c_oe_scl_sda(1, 1, value);
I2C_DELAY(2);
i2c_oe_scl_sda(1, 0, value);
I2C_DELAY(1);
i2c_oe_scl_sda(0, 0, 0); // release line
}
// Call when in the middle of SCL low, advances one clk period
static int i2c_receive_bit(void)
{
int value;
i2c_oe_scl_sda(0, 0, 0);
I2C_DELAY(1);
i2c_oe_scl_sda(0, 1, 0);
I2C_DELAY(1);
// read in the middle of SCL high
value = i2c_r_read() & 1;
I2C_DELAY(1);
i2c_oe_scl_sda(0, 0, 0);
I2C_DELAY(1);
return value;
}
// Send data byte and return 1 if slave sends ACK
static bool i2c_transmit_byte(unsigned char data)
{
int i;
int ack;
// SCL should have already been low for 1/4 cycle
i2c_oe_scl_sda(0, 0, 0);
for (i = 0; i < 8; ++i) {
// MSB first
i2c_transmit_bit((data & (1 << 7)) != 0);
data <<= 1;
}
ack = i2c_receive_bit();
// 0 from slave means ack
return ack == 0;
}
// Read data byte and send ACK if ack=1
static unsigned char i2c_receive_byte(bool ack)
{
int i;
unsigned char data = 0;
for (i = 0; i < 8; ++i) {
data <<= 1;
data |= i2c_receive_bit();
}
i2c_transmit_bit(!ack);
return data;
}
// Reset line state
void i2c_reset(void)
{
int i;
i2c_oe_scl_sda(1, 1, 1);
I2C_DELAY(8);
for (i = 0; i < 9; ++i) {
i2c_oe_scl_sda(1, 0, 1);
I2C_DELAY(2);
i2c_oe_scl_sda(1, 1, 1);
I2C_DELAY(2);
}
i2c_oe_scl_sda(0, 0, 1);
I2C_DELAY(1);
i2c_stop();
i2c_oe_scl_sda(0, 1, 1);
I2C_DELAY(8);
}
/*
* Read slave memory over I2C starting at given address
*
* First writes the memory starting address, then reads the data:
* START WR(slaveaddr) WR(addr) STOP START WR(slaveaddr) RD(data) RD(data) ... STOP
* Some chips require that after transmiting the address, there will be no STOP in between:
* START WR(slaveaddr) WR(addr) START WR(slaveaddr) RD(data) RD(data) ... STOP
*/
bool i2c_read(unsigned char slave_addr, unsigned char addr, unsigned char *data, unsigned int len, bool send_stop)
{
int i;
i2c_start();
if(!i2c_transmit_byte(I2C_ADDR_WR(slave_addr))) {
i2c_stop();
return false;
}
if(!i2c_transmit_byte(addr)) {
i2c_stop();
return false;
}
if (send_stop) {
i2c_stop();
}
i2c_start();
if(!i2c_transmit_byte(I2C_ADDR_RD(slave_addr))) {
i2c_stop();
return false;
}
for (i = 0; i < len; ++i) {
data[i] = i2c_receive_byte(i != len - 1);
}
i2c_stop();
return true;
}
/*
* Write slave memory over I2C starting at given address
*
* First writes the memory starting address, then writes the data:
* START WR(slaveaddr) WR(addr) WR(data) WR(data) ... STOP
*/
bool i2c_write(unsigned char slave_addr, unsigned char addr, const unsigned char *data, unsigned int len)
{
int i;
i2c_start();
if(!i2c_transmit_byte(I2C_ADDR_WR(slave_addr))) {
i2c_stop();
return false;
}
if(!i2c_transmit_byte(addr)) {
i2c_stop();
return false;
}
for (i = 0; i < len; ++i) {
if(!i2c_transmit_byte(data[i])) {
i2c_stop();
return false;
}
}
i2c_stop();
return true;
}
#endif /* CSR_I2C_BASE */

View File

@ -20,6 +20,7 @@ from litex.soc.integration.builder import *
from litex.soc.integration.soc import *
from litedram import modules as litedram_modules
from litedram.modules import parse_spd_hexdump
from litedram.common import *
from litedram.phy.model import SDRAMPHYModel
@ -305,7 +306,7 @@ def main():
parser.add_argument("--sdram-module", default="MT48LC16M16", help="Select SDRAM chip")
parser.add_argument("--sdram-data-width", default=32, help="Set SDRAM chip data width")
parser.add_argument("--sdram-init", default=None, help="SDRAM init file")
parser.add_argument("--sdram-from-spd-data", default=None, help="Generate SDRAM module based on SPD data from file")
parser.add_argument("--sdram-from-spd-dump", default=None, help="Generate SDRAM module based on data from SPD EEPROM dump")
parser.add_argument("--sdram-verbosity", default=0, help="Set SDRAM checker verbosity")
parser.add_argument("--with-ethernet", action="store_true", help="Enable Ethernet support")
parser.add_argument("--with-etherbone", action="store_true", help="Enable Etherbone support")
@ -345,9 +346,8 @@ def main():
soc_kwargs["sdram_module"] = args.sdram_module
soc_kwargs["sdram_data_width"] = int(args.sdram_data_width)
soc_kwargs["sdram_verbosity"] = int(args.sdram_verbosity)
if args.sdram_from_spd_data:
with open(args.sdram_from_spd_data, "rb") as f:
soc_kwargs["sdram_spd_data"] = [int(b) for b in f.read()]
if args.sdram_from_spd_dump:
soc_kwargs["sdram_spd_data"] = parse_spd_hexdump(args.sdram_from_spd_dump)
if args.with_ethernet or args.with_etherbone:
sim_config.add_module("ethernet", "eth", args={"interface": "tap0", "ip": args.remote_ip})