device/dram: add DDR4 RCD I2C access functions
Registering Clock Driver (RCD) is responsible for driving address and
control nets on RDIMM and LRDIMM applications. Its operation is
configurable by a set of Register Control Words (RCWs). There are two
ways of accessing RCWs: in-band on the memory channel as MRS commands
("MR7") or through I2C.
Access through I2C is generic, while MRS commands are passed to memory
controller registers in an implementation-specific way.
See JESD82-31 JEDEC standard for full details.
Change-Id: Ie4e6cfaeae16aba1853b33d527eddebadfbd3887
Signed-off-by: Krystian Hebel <krystian.hebel@3mdeb.com>
Signed-off-by: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/67060
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Martin L Roth <gaumless@gmail.com>
			
			
This commit is contained in:
		
				
					committed by
					
						 Felix Held
						Felix Held
					
				
			
			
				
	
			
			
			
						parent
						
							757e0c1d40
						
					
				
				
					commit
					d3909e1793
				
			| @@ -9,7 +9,7 @@ ramstage-$(CONFIG_USE_DDR5) += ddr5.c | |||||||
| romstage-$(CONFIG_USE_LPDDR4) += lpddr4.c | romstage-$(CONFIG_USE_LPDDR4) += lpddr4.c | ||||||
| ramstage-$(CONFIG_USE_LPDDR4) += lpddr4.c | ramstage-$(CONFIG_USE_LPDDR4) += lpddr4.c | ||||||
|  |  | ||||||
| romstage-$(CONFIG_USE_DDR4) += ddr4.c | romstage-$(CONFIG_USE_DDR4) += ddr4.c rcd.c | ||||||
| ramstage-$(CONFIG_USE_DDR4) += ddr4.c | ramstage-$(CONFIG_USE_DDR4) += ddr4.c | ||||||
|  |  | ||||||
| romstage-$(CONFIG_USE_DDR3) += ddr3.c | romstage-$(CONFIG_USE_DDR3) += ddr3.c | ||||||
|   | |||||||
							
								
								
									
										218
									
								
								src/device/dram/rcd.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								src/device/dram/rcd.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | |||||||
|  | /* SPDX-License-Identifier: GPL-2.0-only */ | ||||||
|  |  | ||||||
|  | #include <console/console.h> | ||||||
|  | #include <device/dram/rcd.h> | ||||||
|  | #include <endian.h> | ||||||
|  | #include <lib.h> | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Registering Clock Driver (RCD) is responsible for driving address and control | ||||||
|  |  * nets on RDIMM and LRDIMM applications. Its operation is configurable by a set | ||||||
|  |  * of Register Control Words (RCWs). There are two ways of accessing RCWs: | ||||||
|  |  * in-band on the memory channel as an MRS commands ("MR7") or through I2C. | ||||||
|  |  * | ||||||
|  |  * From JESD82-31: "For changes to the control word setting, (...) the | ||||||
|  |  * controller needs to wait tMRD after _the last control word access_, before | ||||||
|  |  * further access _to the DRAM_ can take place". MRS is passed to rank 0 of the | ||||||
|  |  * DRAM, but MR7 is reserved so it is ignored by DRAM. tMRD (8nCK) applies here, | ||||||
|  |  * unless longer delay is needed for RCWs which control the clock timing (see | ||||||
|  |  * JESD82-31 for list of such). This makes sense from DRAMs point of view, | ||||||
|  |  * however we are talking to the Registering Clock Driver (RCD), not DRAM. From | ||||||
|  |  * parts marked in the sentence above one may assume that only one delay at the | ||||||
|  |  * end is necessary and RCWs can be written back to back; however, in the same | ||||||
|  |  * document in table 141 tMRD is defined as "Number of clock cycles between two | ||||||
|  |  * control word accesses, MRS accesses, or any DRAM commands". | ||||||
|  |  * | ||||||
|  |  * I2C access to RCWs is required to support byte (8b), word (16b) and double | ||||||
|  |  * word (32b) write size. Bigger blocks are not required. Reads must always be | ||||||
|  |  * 32b, 32b-aligned blocks, even when reading just one RCW. RCD ignores the two | ||||||
|  |  * lowest bits so unaligned accesses would return shifted values. RCWs are | ||||||
|  |  * tightly packed in I2C space, so it is not possible to write just one 4b RCW | ||||||
|  |  * without writing its neighbor. This is especially important for F0RC06, | ||||||
|  |  * Command Space Control Word, as it is able to reset the state of RCD. For this | ||||||
|  |  * reason, the mentioned register has NOP command (all 1's). JESD82-31 does not | ||||||
|  |  * specify timeouts required for such multi-RCWs writes, or any other writes. | ||||||
|  |  * These are not MRS accesses, so it would be strange to apply those timeouts. | ||||||
|  |  * Perhaps only the registers that actually change the clock settings require | ||||||
|  |  * time to stabilize. On the other hand, I2C is relatively slow, so it is | ||||||
|  |  * possible that the write itself is long enough. | ||||||
|  |  * | ||||||
|  |  * RCD I2C address is 0xBx (or 0x58 + DIMM number, depending on convention), it | ||||||
|  |  * is located on the same bus as SPD. It uses a bus command encoding, see | ||||||
|  |  * section 3.3 in JESD82-31 for description of reading and writing register | ||||||
|  |  * values. | ||||||
|  |  * | ||||||
|  |  * This file includes only functions for access through I2C - it is generic, | ||||||
|  |  * while MRS commands are passed to memory controller registers in an | ||||||
|  |  * implementation specific way. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #define RCD_CMD_BEGIN		0x80 | ||||||
|  | #define RCD_CMD_END		0x40 | ||||||
|  | #define RCD_CMD_PEC		0x10 | ||||||
|  | #define RCD_CMD_RD_DWORD	0x00 | ||||||
|  | #define RCD_CMD_WR_BYTE		0x04 | ||||||
|  | #define RCD_CMD_WR_WORD		0x08 | ||||||
|  | #define RCD_CMD_WR_DWORD	0x0C | ||||||
|  | #define RCD_CMD_BUS_BYTE	0x00 | ||||||
|  | #define RCD_CMD_BUS_BLOCK	0x02 | ||||||
|  |  | ||||||
|  | /* Shorthand for block transfers */ | ||||||
|  | #define RCD_CMD_BLOCK	(RCD_CMD_BEGIN | RCD_CMD_END | RCD_CMD_BUS_BLOCK) | ||||||
|  |  | ||||||
|  | /* Excluding size of data */ | ||||||
|  | #define RCD_CMD_BYTES	4 | ||||||
|  |  | ||||||
|  | /* Use byte fields to get rid of endianness issues. */ | ||||||
|  | struct rcd_i2c_cmd { | ||||||
|  | 	uint8_t cmd; | ||||||
|  | 	uint8_t bytes;  /* From next byte up to PEC (excluding) */ | ||||||
|  | 	uint8_t reserved; | ||||||
|  | 	uint8_t devfun; | ||||||
|  | 	uint8_t reg_h; | ||||||
|  | 	uint8_t reg_l; | ||||||
|  | 	union {  /* Not used for reads, can use 1, 2 or 4 for writes */ | ||||||
|  | 		uint8_t bdata; | ||||||
|  | 		uint32_t ddata; | ||||||
|  | 	}; | ||||||
|  | 	/* Optional PEC */ | ||||||
|  | } __packed; | ||||||
|  |  | ||||||
|  | #define RCD_STS_SUCCESS			0x01 | ||||||
|  | #define RCD_STS_INTERNAL_TARGET_ABORT	0x10 | ||||||
|  |  | ||||||
|  | /* Always 4 bytes data + status (for block commands) */ | ||||||
|  | #define RCD_RSP_BYTES	5 | ||||||
|  |  | ||||||
|  | struct rcd_i2c_rsp { | ||||||
|  | 	uint8_t bytes;  /* From next byte up to PEC (excluding) */ | ||||||
|  | 	uint8_t status; | ||||||
|  | 	union { | ||||||
|  | 		uint8_t bdata; | ||||||
|  | 		uint32_t ddata; | ||||||
|  | 	}; | ||||||
|  | 	/* Optional PEC */ | ||||||
|  | } __packed; | ||||||
|  |  | ||||||
|  | /* Reads a register storing its value in the host's byte order. Returns non-zero on success. */ | ||||||
|  | static int rcd_readd(unsigned int bus, uint8_t slave, uint8_t reg, uint32_t *data) | ||||||
|  | { | ||||||
|  | 	struct i2c_msg seg[2]; | ||||||
|  | 	struct rcd_i2c_cmd cmd = { | ||||||
|  | 		.cmd = RCD_CMD_BLOCK | RCD_CMD_RD_DWORD, | ||||||
|  | 		.bytes = RCD_CMD_BYTES, | ||||||
|  | 		.reg_l = reg | ||||||
|  | 	}; | ||||||
|  | 	struct rcd_i2c_rsp rsp = { 0xaa, 0x55 }; | ||||||
|  |  | ||||||
|  | 	seg[0].flags = 0; | ||||||
|  | 	seg[0].slave = slave; | ||||||
|  | 	seg[0].buf   = (uint8_t *)&cmd; | ||||||
|  | 	seg[0].len   = cmd.bytes + 2;  /* + .cmd and .bytes fields */ | ||||||
|  |  | ||||||
|  | 	i2c_transfer(bus, seg, 1); | ||||||
|  |  | ||||||
|  | 	seg[0].len   = 1;	/* Send just the command again */ | ||||||
|  | 	seg[1].flags = I2C_M_RD; | ||||||
|  | 	seg[1].slave = slave; | ||||||
|  | 	seg[1].buf   = (uint8_t *)&rsp; | ||||||
|  | 	seg[1].len   = RCD_RSP_BYTES + 1;  /* + .bytes field */ | ||||||
|  |  | ||||||
|  | 	i2c_transfer(bus, seg, ARRAY_SIZE(seg)); | ||||||
|  |  | ||||||
|  | 	/* Data is sent MSB to LSB, i.e. higher registers to lower. */ | ||||||
|  | 	*data = be32toh(rsp.ddata); | ||||||
|  |  | ||||||
|  | 	return rsp.status == RCD_STS_SUCCESS; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int rcd_writed(unsigned int bus, uint8_t slave, uint8_t reg, uint32_t data) | ||||||
|  | { | ||||||
|  | 	struct i2c_msg seg; | ||||||
|  | 	struct rcd_i2c_cmd cmd = { | ||||||
|  | 		.cmd = RCD_CMD_BLOCK | RCD_CMD_WR_DWORD, | ||||||
|  | 		.bytes = RCD_CMD_BYTES + sizeof(data), | ||||||
|  | 		.reg_l = reg, | ||||||
|  | 		/* Data is sent MSB to LSB, i.e. higher registers to lower. */ | ||||||
|  | 		.ddata = htobe32(data) | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	seg.flags = 0; | ||||||
|  | 	seg.slave = slave; | ||||||
|  | 	seg.buf   = (uint8_t *)&cmd; | ||||||
|  | 	seg.len   = cmd.bytes + 2;  /* + .cmd and .bytes fields */ | ||||||
|  |  | ||||||
|  | 	return i2c_transfer(bus, &seg, 1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int rcd_writeb(unsigned int bus, uint8_t slave, uint8_t reg, uint8_t data) | ||||||
|  | { | ||||||
|  | 	struct i2c_msg seg; | ||||||
|  | 	struct rcd_i2c_cmd cmd = { | ||||||
|  | 		.cmd = RCD_CMD_BLOCK | RCD_CMD_WR_BYTE, | ||||||
|  | 		.bytes = RCD_CMD_BYTES + sizeof(data), | ||||||
|  | 		.reg_l = reg, | ||||||
|  | 		.bdata = data | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	seg.flags = 0; | ||||||
|  | 	seg.slave = slave; | ||||||
|  | 	seg.buf   = (uint8_t *)&cmd; | ||||||
|  | 	seg.len   = cmd.bytes + 2;  /* + .cmd and .bytes fields */ | ||||||
|  |  | ||||||
|  | 	return i2c_transfer(bus, &seg, 1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int rcd_write_reg(unsigned int bus, uint8_t slave, enum rcw_idx reg, | ||||||
|  | 		  uint8_t data) | ||||||
|  | { | ||||||
|  | 	if (reg < F0RC00_01 || reg > F0RCFx) { | ||||||
|  | 		printk(BIOS_ERR, "Trying to write to illegal RCW %#2.2x\n", | ||||||
|  | 		       reg); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return rcd_writeb(bus, slave, reg, data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int rcd_write_32b(unsigned int bus, uint8_t slave, enum rcw_idx reg, | ||||||
|  | 		  uint32_t data) | ||||||
|  | { | ||||||
|  | 	if (reg < F0RC00_01 || reg > F0RCFx) { | ||||||
|  | 		printk(BIOS_ERR, "Trying to write to illegal RCW %#2.2x\n", | ||||||
|  | 		       reg); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (reg & 3) { | ||||||
|  | 		/* | ||||||
|  | 		 * RCD would silently mask out the lowest bits, assume that this | ||||||
|  | 		 * is not what caller wanted. | ||||||
|  | 		 */ | ||||||
|  | 		printk(BIOS_ERR, "Unaligned RCW %#2.2x, aborting\n", reg); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return rcd_writed(bus, slave, reg, data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void dump_rcd(unsigned int bus, u8 addr) | ||||||
|  | { | ||||||
|  | 	/* Can only read in 32b chunks */ | ||||||
|  | 	uint8_t buf[RCW_ALL_ALIGNED]; | ||||||
|  | 	int i; | ||||||
|  |  | ||||||
|  | 	for (i = 0; i < RCW_ALL_ALIGNED; i += sizeof(uint32_t)) { | ||||||
|  | 		uint32_t data; | ||||||
|  | 		if (!rcd_readd(bus, addr, i, &data)) { | ||||||
|  | 			printk(BIOS_ERR, "Failed to read RCD (%d-%02x) at offset %#2.2x\n", | ||||||
|  | 			       bus, addr, i); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		/* We want to dump memory the way it's stored, so make sure it's in LE. */ | ||||||
|  | 		*(uint32_t *)&buf[i] = htole32(data); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	printk(BIOS_DEBUG, "RCD dump for I2C address %#2.2x:\n", addr); | ||||||
|  | 	hexdump(buf, sizeof(buf)); | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								src/include/device/dram/rcd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/include/device/dram/rcd.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||||
|  |  | ||||||
|  | #ifndef DEVICE_DRAM_RCD_H | ||||||
|  | #define DEVICE_DRAM_RCD_H | ||||||
|  |  | ||||||
|  | #include <types.h> | ||||||
|  | #include <device/i2c_simple.h> | ||||||
|  | #include <console/console.h> | ||||||
|  |  | ||||||
|  | enum rcw_idx { | ||||||
|  | 	VEN_ID_L, | ||||||
|  | 	VEN_ID_H, | ||||||
|  | 	DEV_ID_L, | ||||||
|  | 	DEV_ID_H, | ||||||
|  | 	REV_ID, | ||||||
|  | 	RES_05, | ||||||
|  | 	RES_06, | ||||||
|  | 	RES_07, | ||||||
|  | 	F0RC00_01, | ||||||
|  | 	F0RC02_03, | ||||||
|  | 	F0RC04_05, | ||||||
|  | 	F0RC06_07, | ||||||
|  | 	F0RC08_09, | ||||||
|  | 	F0RC0A_0B, | ||||||
|  | 	F0RC0C_0D, | ||||||
|  | 	F0RC0E_0F, | ||||||
|  | 	F0RC1x, | ||||||
|  | 	F0RC2x, | ||||||
|  | 	F0RC3x, | ||||||
|  | 	F0RC4x, | ||||||
|  | 	F0RC5x, | ||||||
|  | 	F0RC6x, | ||||||
|  | 	F0RC7x, | ||||||
|  | 	F0RC8x, | ||||||
|  | 	F0RC9x, | ||||||
|  | 	F0RCAx, | ||||||
|  | 	F0RCBx, | ||||||
|  | 	F0RCCx, | ||||||
|  | 	F0RCDx, | ||||||
|  | 	F0RCEx, | ||||||
|  | 	F0RCFx, | ||||||
|  | 	RCW_ALL,	/* Total num of bytes */ | ||||||
|  | 	RCW_ALL_ALIGNED	/* Total num of bytes after aligning to 4B */ | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _Static_assert(RCW_ALL_ALIGNED % sizeof(uint32_t) == 0, | ||||||
|  | 	       "RCW_ALL_ALIGNED is not aligned"); | ||||||
|  |  | ||||||
|  | /* Write an 8-bit register. Returns the number of written bytes. */ | ||||||
|  | int rcd_write_reg(unsigned int bus, uint8_t slave, enum rcw_idx reg, | ||||||
|  | 		  uint8_t data); | ||||||
|  |  | ||||||
|  | /* Write 32 bits of memory (i.e., four 8-bit registers, not 1 32-bit register, which would | ||||||
|  |  * involve byte swapping). Returns the number of written bytes. */ | ||||||
|  | int rcd_write_32b(unsigned int bus, uint8_t slave, enum rcw_idx reg, | ||||||
|  | 		  uint32_t data); | ||||||
|  |  | ||||||
|  | /* Dump 32 bytes of RCD onto the screen. */ | ||||||
|  | void dump_rcd(unsigned int bus, uint8_t addr); | ||||||
|  |  | ||||||
|  | #endif /* DEVICE_DRAM_RCD_H */ | ||||||
		Reference in New Issue
	
	Block a user