aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/amd/amdgpu/amdgpu_eeprom.c
blob: 4d9eb0137f8c4384fe22cead2d6a7acecdcdbb68 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/*
 * Copyright 2021 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "amdgpu_eeprom.h"
#include "amdgpu.h"

/* AT24CM02 and M24M02-R have a 256-byte write page size.
 */
#define EEPROM_PAGE_BITS   8
#define EEPROM_PAGE_SIZE   (1U << EEPROM_PAGE_BITS)
#define EEPROM_PAGE_MASK   (EEPROM_PAGE_SIZE - 1)

#define EEPROM_OFFSET_SIZE 2

/* EEPROM memory addresses are 19-bits long, which can
 * be partitioned into 3, 8, 8 bits, for a total of 19.
 * The upper 3 bits are sent as part of the 7-bit
 * "Device Type Identifier"--an I2C concept, which for EEPROM devices
 * is hard-coded as 1010b, indicating that it is an EEPROM
 * device--this is the wire format, followed by the upper
 * 3 bits of the 19-bit address, followed by the direction,
 * followed by two bytes holding the rest of the 16-bits of
 * the EEPROM memory address. The format on the wire for EEPROM
 * devices is: 1010XYZD, A15:A8, A7:A0,
 * Where D is the direction and sequenced out by the hardware.
 * Bits XYZ are memory address bits 18, 17 and 16.
 * These bits are compared to how pins 1-3 of the part are connected,
 * depending on the size of the part, more on that later.
 *
 * Note that of this wire format, a client is in control
 * of, and needs to specify only XYZ, A15:A8, A7:0, bits,
 * which is exactly the EEPROM memory address, or offset,
 * in order to address up to 8 EEPROM devices on the I2C bus.
 *
 * For instance, a 2-Mbit I2C EEPROM part, addresses all its bytes,
 * using an 18-bit address, bit 17 to 0 and thus would use all but one bit of
 * the 19 bits previously mentioned. The designer would then not connect
 * pins 1 and 2, and pin 3 usually named "A_2" or "E2", would be connected to
 * either Vcc or GND. This would allow for up to two 2-Mbit parts on
 * the same bus, where one would be addressable with bit 18 as 1, and
 * the other with bit 18 of the address as 0.
 *
 * For a 2-Mbit part, bit 18 is usually known as the "Chip Enable" or
 * "Hardware Address Bit". This bit is compared to the load on pin 3
 * of the device, described above, and if there is a match, then this
 * device responds to the command. This way, you can connect two
 * 2-Mbit EEPROM devices on the same bus, but see one contiguous
 * memory from 0 to 7FFFFh, where address 0 to 3FFFF is in the device
 * whose pin 3 is connected to GND, and address 40000 to 7FFFFh is in
 * the 2nd device, whose pin 3 is connected to Vcc.
 *
 * This addressing you encode in the 32-bit "eeprom_addr" below,
 * namely the 19-bits "XYZ,A15:A0", as a single 19-bit address. For
 * instance, eeprom_addr = 0x6DA01, is 110_1101_1010_0000_0001, where
 * XYZ=110b, and A15:A0=DA01h. The XYZ bits become part of the device
 * address, and the rest of the address bits are sent as the memory
 * address bytes.
 *
 * That is, for an I2C EEPROM driver everything is controlled by
 * the "eeprom_addr".
 *
 * P.S. If you need to write, lock and read the Identification Page,
 * (M24M02-DR device only, which we do not use), change the "7" to
 * "0xF" in the macro below, and let the client set bit 20 to 1 in
 * "eeprom_addr", and set A10 to 0 to write into it, and A10 and A1 to
 * 1 to lock it permanently.
 */
#define MAKE_I2C_ADDR(_aa) ((0xA << 3) | (((_aa) >> 16) & 7))

static int __amdgpu_eeprom_xfer(struct i2c_adapter *i2c_adap, u32 eeprom_addr,
				u8 *eeprom_buf, u16 buf_size, bool read)
{
	u8 eeprom_offset_buf[EEPROM_OFFSET_SIZE];
	struct i2c_msg msgs[] = {
		{
			.flags = 0,
			.len = EEPROM_OFFSET_SIZE,
			.buf = eeprom_offset_buf,
		},
		{
			.flags = read ? I2C_M_RD : 0,
		},
	};
	const u8 *p = eeprom_buf;
	int r;
	u16 len;

	for (r = 0; buf_size > 0;
	      buf_size -= len, eeprom_addr += len, eeprom_buf += len) {
		/* Set the EEPROM address we want to write to/read from.
		 */
		msgs[0].addr = MAKE_I2C_ADDR(eeprom_addr);
		msgs[1].addr = msgs[0].addr;
		msgs[0].buf[0] = (eeprom_addr >> 8) & 0xff;
		msgs[0].buf[1] = eeprom_addr & 0xff;

		if (!read) {
			/* Write the maximum amount of data, without
			 * crossing the device's page boundary, as per
			 * its spec. Partial page writes are allowed,
			 * starting at any location within the page,
			 * so long as the page boundary isn't crossed
			 * over (actually the page pointer rolls
			 * over).
			 *
			 * As per the AT24CM02 EEPROM spec, after
			 * writing into a page, the I2C driver should
			 * terminate the transfer, i.e. in
			 * "i2c_transfer()" below, with a STOP
			 * condition, so that the self-timed write
			 * cycle begins. This is implied for the
			 * "i2c_transfer()" abstraction.
			 */
			len = min(EEPROM_PAGE_SIZE - (eeprom_addr &
						      EEPROM_PAGE_MASK),
				  (u32)buf_size);
		} else {
			/* Reading from the EEPROM has no limitation
			 * on the number of bytes read from the EEPROM
			 * device--they are simply sequenced out.
			 */
			len = buf_size;
		}
		msgs[1].len = len;
		msgs[1].buf = eeprom_buf;

		/* This constitutes a START-STOP transaction.
		 */
		r = i2c_transfer(i2c_adap, msgs, ARRAY_SIZE(msgs));
		if (r != ARRAY_SIZE(msgs))
			break;

		if (!read) {
			/* According to EEPROM specs the length of the
			 * self-writing cycle, tWR (tW), is 10 ms.
			 *
			 * TODO: Use polling on ACK, aka Acknowledge
			 * Polling, to minimize waiting for the
			 * internal write cycle to complete, as it is
			 * usually smaller than tWR (tW).
			 */
			msleep(10);
		}
	}

	return r < 0 ? r : eeprom_buf - p;
}

/**
 * amdgpu_eeprom_xfer -- Read/write from/to an I2C EEPROM device
 * @i2c_adap: pointer to the I2C adapter to use
 * @eeprom_addr: EEPROM address from which to read/write
 * @eeprom_buf: pointer to data buffer to read into/write from
 * @buf_size: the size of @eeprom_buf
 * @read: True if reading from the EEPROM, false if writing
 *
 * Returns the number of bytes read/written; -errno on error.
 */
static int amdgpu_eeprom_xfer(struct i2c_adapter *i2c_adap, u32 eeprom_addr,
			      u8 *eeprom_buf, u16 buf_size, bool read)
{
	const struct i2c_adapter_quirks *quirks = i2c_adap->quirks;
	u16 limit;

	if (!quirks)
		limit = 0;
	else if (read)
		limit = quirks->max_read_len;
	else
		limit = quirks->max_write_len;

	if (limit == 0) {
		return __amdgpu_eeprom_xfer(i2c_adap, eeprom_addr,
					    eeprom_buf, buf_size, read);
	} else if (limit <= EEPROM_OFFSET_SIZE) {
		dev_err_ratelimited(&i2c_adap->dev,
				    "maddr:0x%04X size:0x%02X:quirk max_%s_len must be > %d",
				    eeprom_addr, buf_size,
				    read ? "read" : "write", EEPROM_OFFSET_SIZE);
		return -EINVAL;
	} else {
		u16 ps; /* Partial size */
		int res = 0, r;

		/* The "limit" includes all data bytes sent/received,
		 * which would include the EEPROM_OFFSET_SIZE bytes.
		 * Account for them here.
		 */
		limit -= EEPROM_OFFSET_SIZE;
		for ( ; buf_size > 0;
		      buf_size -= ps, eeprom_addr += ps, eeprom_buf += ps) {
			ps = min(limit, buf_size);

			r = __amdgpu_eeprom_xfer(i2c_adap, eeprom_addr,
						 eeprom_buf, ps, read);
			if (r < 0)
				return r;
			res += r;
		}

		return res;
	}
}

int amdgpu_eeprom_read(struct i2c_adapter *i2c_adap,
		       u32 eeprom_addr, u8 *eeprom_buf,
		       u16 bytes)
{
	return amdgpu_eeprom_xfer(i2c_adap, eeprom_addr, eeprom_buf, bytes,
				  true);
}

int amdgpu_eeprom_write(struct i2c_adapter *i2c_adap,
			u32 eeprom_addr, u8 *eeprom_buf,
			u16 bytes)
{
	return amdgpu_eeprom_xfer(i2c_adap, eeprom_addr, eeprom_buf, bytes,
				  false);
}