diff --git a/litex/soc/software/liblitedram/Makefile b/litex/soc/software/liblitedram/Makefile index 070263252..46d71339a 100644 --- a/litex/soc/software/liblitedram/Makefile +++ b/litex/soc/software/liblitedram/Makefile @@ -1,7 +1,7 @@ include ../include/generated/variables.mak include $(SOC_DIRECTORY)/software/common.mak -OBJECTS=sdram.o +OBJECTS = sdram.o spd.o all: liblitedram.a diff --git a/litex/soc/software/liblitedram/sdram.h b/litex/soc/software/liblitedram/sdram.h index 21433c03c..0c8cae9e6 100644 --- a/litex/soc/software/liblitedram/sdram.h +++ b/litex/soc/software/liblitedram/sdram.h @@ -2,6 +2,7 @@ #define __SDRAM_H #include +#include "spd.h" void sdrsw(void); void sdrhw(void); diff --git a/litex/soc/software/liblitedram/spd.c b/litex/soc/software/liblitedram/spd.c new file mode 100644 index 000000000..3f396e782 --- /dev/null +++ b/litex/soc/software/liblitedram/spd.c @@ -0,0 +1,180 @@ +// This file is Copyright (c) 2020 Antmicro + +#include +#include "spd.h" + +#ifdef CSR_I2C_BASE + +// SMBus uses frequency 10-100 kHz +#define I2C_FREQ_HZ 50000 +#define I2C_PERIOD_CYCLES (CONFIG_CLOCK_FREQUENCY / I2C_FREQ_HZ) +#define I2C_DELAY(n) cdelay((n)*I2C_PERIOD_CYCLES/4) + +static void cdelay(int i) +{ + while(i > 0) { + __asm__ volatile(CONFIG_CPU_NOP); + i--; + } +} + +static void i2c_oe_scl_sda(int oe, int scl, int 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); +} + +// Reset line state +static 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); +} + +// 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 int i2c_transmit(unsigned char data) +{ + int ack; + int i; + + // 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(int ack) +{ + unsigned char data = 0; + int i; + + i2c_oe_scl_sda(0, 0, 0); + I2C_DELAY(1); + for (i = 0; i < 8; ++i) { + data <<= 1; + data |= i2c_receive_bit(); + } + i2c_transmit_bit(!ack); + + return data; +} + + +#define ADDR_PREAMBLE_RW 0b1010 +#define ADDR_7BIT(addr) ((ADDR_PREAMBLE_RW << 3) | ((addr) & 0b111)) +#define ADDR_WRITE(addr) ((ADDR_7BIT(addr) << 1) & (~1u)) +#define ADDR_READ(addr) ((ADDR_7BIT(addr) << 1) | 1u) + +/* + * Read SPD memory content + * + * spdaddr: address of SPD EEPROM defined by pins A0, A1, A2 + * addr: memory starting address + */ +int spdread(unsigned int spdaddr, unsigned int addr, unsigned char *buf, unsigned int len) { + int i; + + i2c_reset(); + + // To read from random address, we have to first send a "data-less" WRITE, + // followed by START condition with a READ (no STOP condition) + i2c_start(); + + if(!i2c_transmit(ADDR_WRITE(spdaddr))) { + i2c_reset(); + return 0; + } + if(!i2c_transmit(addr)) { + i2c_reset(); + return 0; + } + + I2C_DELAY(1); + i2c_start(); + if(!i2c_transmit(ADDR_READ(spdaddr))) { + i2c_reset(); + return 0; + } + for (i = 0; i < len; ++i) { + buf[i] = i2c_receive(i != len - 1); + } + i2c_stop(); + + return 1; +} +#endif /* CSR_I2C_BASE */ diff --git a/litex/soc/software/liblitedram/spd.h b/litex/soc/software/liblitedram/spd.h new file mode 100644 index 000000000..54b04e349 --- /dev/null +++ b/litex/soc/software/liblitedram/spd.h @@ -0,0 +1,8 @@ +#ifndef __SPD_H +#define __SPD_H + +#include + +int spdread(unsigned int spdaddr, unsigned int addr, unsigned char *buf, unsigned int len); + +#endif /* __SPD_H */