xref: /aosp_15_r20/external/coreboot/src/device/dram/rcd.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <console/console.h>
4 #include <device/dram/rcd.h>
5 #include <endian.h>
6 #include <lib.h>
7 
8 /**
9  * Registering Clock Driver (RCD) is responsible for driving address and control
10  * nets on RDIMM and LRDIMM applications. Its operation is configurable by a set
11  * of Register Control Words (RCWs). There are two ways of accessing RCWs:
12  * in-band on the memory channel as an MRS commands ("MR7") or through I2C.
13  *
14  * From JESD82-31: "For changes to the control word setting, (...) the
15  * controller needs to wait tMRD after _the last control word access_, before
16  * further access _to the DRAM_ can take place". MRS is passed to rank 0 of the
17  * DRAM, but MR7 is reserved so it is ignored by DRAM. tMRD (8nCK) applies here,
18  * unless longer delay is needed for RCWs which control the clock timing (see
19  * JESD82-31 for list of such). This makes sense from DRAMs point of view,
20  * however we are talking to the Registering Clock Driver (RCD), not DRAM. From
21  * parts marked in the sentence above one may assume that only one delay at the
22  * end is necessary and RCWs can be written back to back; however, in the same
23  * document in table 141 tMRD is defined as "Number of clock cycles between two
24  * control word accesses, MRS accesses, or any DRAM commands".
25  *
26  * I2C access to RCWs is required to support byte (8b), word (16b) and double
27  * word (32b) write size. Bigger blocks are not required. Reads must always be
28  * 32b, 32b-aligned blocks, even when reading just one RCW. RCD ignores the two
29  * lowest bits so unaligned accesses would return shifted values. RCWs are
30  * tightly packed in I2C space, so it is not possible to write just one 4b RCW
31  * without writing its neighbor. This is especially important for F0RC06,
32  * Command Space Control Word, as it is able to reset the state of RCD. For this
33  * reason, the mentioned register has NOP command (all 1's). JESD82-31 does not
34  * specify timeouts required for such multi-RCWs writes, or any other writes.
35  * These are not MRS accesses, so it would be strange to apply those timeouts.
36  * Perhaps only the registers that actually change the clock settings require
37  * time to stabilize. On the other hand, I2C is relatively slow, so it is
38  * possible that the write itself is long enough.
39  *
40  * RCD I2C address is 0xBx (or 0x58 + DIMM number, depending on convention), it
41  * is located on the same bus as SPD. It uses a bus command encoding, see
42  * section 3.3 in JESD82-31 for description of reading and writing register
43  * values.
44  *
45  * This file includes only functions for access through I2C - it is generic,
46  * while MRS commands are passed to memory controller registers in an
47  * implementation specific way.
48  */
49 
50 #define RCD_CMD_BEGIN		0x80
51 #define RCD_CMD_END		0x40
52 #define RCD_CMD_PEC		0x10
53 #define RCD_CMD_RD_DWORD	0x00
54 #define RCD_CMD_WR_BYTE		0x04
55 #define RCD_CMD_WR_WORD		0x08
56 #define RCD_CMD_WR_DWORD	0x0C
57 #define RCD_CMD_BUS_BYTE	0x00
58 #define RCD_CMD_BUS_BLOCK	0x02
59 
60 /* Shorthand for block transfers */
61 #define RCD_CMD_BLOCK	(RCD_CMD_BEGIN | RCD_CMD_END | RCD_CMD_BUS_BLOCK)
62 
63 /* Excluding size of data */
64 #define RCD_CMD_BYTES	4
65 
66 /* Use byte fields to get rid of endianness issues. */
67 struct rcd_i2c_cmd {
68 	uint8_t cmd;
69 	uint8_t bytes;  /* From next byte up to PEC (excluding) */
70 	uint8_t reserved;
71 	uint8_t devfun;
72 	uint8_t reg_h;
73 	uint8_t reg_l;
74 	union {  /* Not used for reads, can use 1, 2 or 4 for writes */
75 		uint8_t bdata;
76 		uint32_t ddata;
77 	};
78 	/* Optional PEC */
79 } __packed;
80 
81 #define RCD_STS_SUCCESS			0x01
82 #define RCD_STS_INTERNAL_TARGET_ABORT	0x10
83 
84 /* Always 4 bytes data + status (for block commands) */
85 #define RCD_RSP_BYTES	5
86 
87 struct rcd_i2c_rsp {
88 	uint8_t bytes;  /* From next byte up to PEC (excluding) */
89 	uint8_t status;
90 	union {
91 		uint8_t bdata;
92 		uint32_t ddata;
93 	};
94 	/* Optional PEC */
95 } __packed;
96 
97 /* Reads a register storing its value in the host's byte order. Returns non-zero on success. */
rcd_readd(unsigned int bus,uint8_t slave,uint8_t reg,uint32_t * data)98 static int rcd_readd(unsigned int bus, uint8_t slave, uint8_t reg, uint32_t *data)
99 {
100 	struct i2c_msg seg[2];
101 	struct rcd_i2c_cmd cmd = {
102 		.cmd = RCD_CMD_BLOCK | RCD_CMD_RD_DWORD,
103 		.bytes = RCD_CMD_BYTES,
104 		.reg_l = reg
105 	};
106 	struct rcd_i2c_rsp rsp = { 0xaa, 0x55 };
107 
108 	seg[0].flags = 0;
109 	seg[0].slave = slave;
110 	seg[0].buf   = (uint8_t *)&cmd;
111 	seg[0].len   = cmd.bytes + 2;  /* + .cmd and .bytes fields */
112 
113 	i2c_transfer(bus, seg, 1);
114 
115 	seg[0].len   = 1;	/* Send just the command again */
116 	seg[1].flags = I2C_M_RD;
117 	seg[1].slave = slave;
118 	seg[1].buf   = (uint8_t *)&rsp;
119 	seg[1].len   = RCD_RSP_BYTES + 1;  /* + .bytes field */
120 
121 	i2c_transfer(bus, seg, ARRAY_SIZE(seg));
122 
123 	/* Data is sent MSB to LSB, i.e. higher registers to lower. */
124 	*data = be32toh(rsp.ddata);
125 
126 	return rsp.status == RCD_STS_SUCCESS;
127 }
128 
rcd_writed(unsigned int bus,uint8_t slave,uint8_t reg,uint32_t data)129 static int rcd_writed(unsigned int bus, uint8_t slave, uint8_t reg, uint32_t data)
130 {
131 	struct i2c_msg seg;
132 	struct rcd_i2c_cmd cmd = {
133 		.cmd = RCD_CMD_BLOCK | RCD_CMD_WR_DWORD,
134 		.bytes = RCD_CMD_BYTES + sizeof(data),
135 		.reg_l = reg,
136 		/* Data is sent MSB to LSB, i.e. higher registers to lower. */
137 		.ddata = htobe32(data)
138 	};
139 
140 	seg.flags = 0;
141 	seg.slave = slave;
142 	seg.buf   = (uint8_t *)&cmd;
143 	seg.len   = cmd.bytes + 2;  /* + .cmd and .bytes fields */
144 
145 	return i2c_transfer(bus, &seg, 1);
146 }
147 
rcd_writeb(unsigned int bus,uint8_t slave,uint8_t reg,uint8_t data)148 static int rcd_writeb(unsigned int bus, uint8_t slave, uint8_t reg, uint8_t data)
149 {
150 	struct i2c_msg seg;
151 	struct rcd_i2c_cmd cmd = {
152 		.cmd = RCD_CMD_BLOCK | RCD_CMD_WR_BYTE,
153 		.bytes = RCD_CMD_BYTES + sizeof(data),
154 		.reg_l = reg,
155 		.bdata = data
156 	};
157 
158 	seg.flags = 0;
159 	seg.slave = slave;
160 	seg.buf   = (uint8_t *)&cmd;
161 	seg.len   = cmd.bytes + 2;  /* + .cmd and .bytes fields */
162 
163 	return i2c_transfer(bus, &seg, 1);
164 }
165 
rcd_write_reg(unsigned int bus,uint8_t slave,enum rcw_idx reg,uint8_t data)166 int rcd_write_reg(unsigned int bus, uint8_t slave, enum rcw_idx reg,
167 		  uint8_t data)
168 {
169 	if (reg < F0RC00_01 || reg > F0RCFx) {
170 		printk(BIOS_ERR, "Trying to write to illegal RCW %#2.2x\n",
171 		       reg);
172 		return 0;
173 	}
174 
175 	return rcd_writeb(bus, slave, reg, data);
176 }
177 
rcd_write_32b(unsigned int bus,uint8_t slave,enum rcw_idx reg,uint32_t data)178 int rcd_write_32b(unsigned int bus, uint8_t slave, enum rcw_idx reg,
179 		  uint32_t data)
180 {
181 	if (reg < F0RC00_01 || reg > F0RCFx) {
182 		printk(BIOS_ERR, "Trying to write to illegal RCW %#2.2x\n",
183 		       reg);
184 		return 0;
185 	}
186 
187 	if (reg & 3) {
188 		/*
189 		 * RCD would silently mask out the lowest bits, assume that this
190 		 * is not what caller wanted.
191 		 */
192 		printk(BIOS_ERR, "Unaligned RCW %#2.2x, aborting\n", reg);
193 		return 0;
194 	}
195 
196 	return rcd_writed(bus, slave, reg, data);
197 }
198 
dump_rcd(unsigned int bus,u8 addr)199 void dump_rcd(unsigned int bus, u8 addr)
200 {
201 	/* Can only read in 32b chunks */
202 	uint8_t buf[RCW_ALL_ALIGNED];
203 	int i;
204 
205 	for (i = 0; i < RCW_ALL_ALIGNED; i += sizeof(uint32_t)) {
206 		uint32_t data;
207 		if (!rcd_readd(bus, addr, i, &data)) {
208 			printk(BIOS_ERR, "Failed to read RCD (%d-%02x) at offset %#2.2x\n",
209 			       bus, addr, i);
210 			return;
211 		}
212 		/* We want to dump memory the way it's stored, so make sure it's in LE. */
213 		*(uint32_t *)&buf[i] = htole32(data);
214 	}
215 
216 	printk(BIOS_DEBUG, "RCD dump for I2C address %#2.2x:\n", addr);
217 	hexdump(buf, sizeof(buf));
218 }
219