Commit | Line | Data |
---|---|---|
bd607166 KR |
1 | /* |
2 | * Copyright 2019 Advanced Micro Devices, Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | */ | |
1ea2b260 KR |
23 | #include <linux/pci.h> |
24 | ||
bd607166 KR |
25 | #include "amdgpu.h" |
26 | #include "amdgpu_i2c.h" | |
27 | #include "smu_v11_0_i2c.h" | |
28 | #include "atom.h" | |
d43f7ff6 | 29 | #include "amdgpu_fru_eeprom.h" |
25e5c09f | 30 | #include "amdgpu_eeprom.h" |
bd607166 | 31 | |
afbe5d1e LT |
32 | #define FRU_EEPROM_MADDR_6 0x60000 |
33 | #define FRU_EEPROM_MADDR_8 0x80000 | |
bd607166 | 34 | |
afbe5d1e | 35 | static bool is_fru_eeprom_supported(struct amdgpu_device *adev, u32 *fru_addr) |
fabe01d7 | 36 | { |
f94582e4 KR |
37 | /* Only server cards have the FRU EEPROM |
38 | * TODO: See if we can figure this out dynamically instead of | |
39 | * having to parse VBIOS versions. | |
1ea2b260 | 40 | */ |
f94582e4 KR |
41 | struct atom_context *atom_ctx = adev->mode_info.atom_context; |
42 | ||
901abf36 | 43 | /* The i2c access is blocked on VF |
44 | * TODO: Need other way to get the info | |
a8558fce | 45 | * Also, FRU not valid for APU devices. |
901abf36 | 46 | */ |
a8558fce | 47 | if (amdgpu_sriov_vf(adev) || (adev->flags & AMD_IS_APU)) |
901abf36 | 48 | return false; |
49 | ||
afbe5d1e LT |
50 | /* The default I2C EEPROM address of the FRU. |
51 | */ | |
52 | if (fru_addr) | |
53 | *fru_addr = FRU_EEPROM_MADDR_8; | |
54 | ||
3ed89339 | 55 | /* VBIOS is of the format ###-DXXXYYYY-##. For SKU identification, |
f94582e4 KR |
56 | * we can use just the "DXXX" portion. If there were more models, we |
57 | * could convert the 3 characters to a hex integer and use a switch | |
58 | * for ease/speed/readability. For now, 2 string comparisons are | |
59 | * reasonable and not too expensive | |
60 | */ | |
be2e8aca YW |
61 | switch (amdgpu_ip_version(adev, MP1_HWIP, 0)) { |
62 | case IP_VERSION(11, 0, 2): | |
63 | switch (adev->asic_type) { | |
64 | case CHIP_VEGA20: | |
65 | /* D161 and D163 are the VG20 server SKUs */ | |
66 | if (strnstr(atom_ctx->vbios_pn, "D161", | |
67 | sizeof(atom_ctx->vbios_pn)) || | |
68 | strnstr(atom_ctx->vbios_pn, "D163", | |
69 | sizeof(atom_ctx->vbios_pn))) { | |
70 | if (fru_addr) | |
71 | *fru_addr = FRU_EEPROM_MADDR_6; | |
72 | return true; | |
73 | } else { | |
74 | return false; | |
75 | } | |
76 | case CHIP_ARCTURUS: | |
77 | default: | |
f94582e4 | 78 | return false; |
afbe5d1e | 79 | } |
be2e8aca | 80 | case IP_VERSION(11, 0, 7): |
adf64e21 ML |
81 | if (strnstr(atom_ctx->vbios_pn, "D603", |
82 | sizeof(atom_ctx->vbios_pn))) { | |
83 | if (strnstr(atom_ctx->vbios_pn, "D603GLXE", | |
84 | sizeof(atom_ctx->vbios_pn))) { | |
c8fea927 | 85 | return false; |
afbe5d1e | 86 | } |
ce83aa7b SS |
87 | |
88 | if (fru_addr) | |
89 | *fru_addr = FRU_EEPROM_MADDR_6; | |
90 | return true; | |
91 | ||
c8fea927 | 92 | } else { |
3ed89339 | 93 | return false; |
c8fea927 | 94 | } |
be2e8aca YW |
95 | case IP_VERSION(13, 0, 2): |
96 | /* All Aldebaran SKUs have an FRU */ | |
97 | if (!strnstr(atom_ctx->vbios_pn, "D673", | |
98 | sizeof(atom_ctx->vbios_pn))) | |
99 | if (fru_addr) | |
100 | *fru_addr = FRU_EEPROM_MADDR_6; | |
101 | return true; | |
102 | case IP_VERSION(13, 0, 6): | |
103 | if (fru_addr) | |
104 | *fru_addr = FRU_EEPROM_MADDR_8; | |
105 | return true; | |
f94582e4 KR |
106 | default: |
107 | return false; | |
108 | } | |
fabe01d7 JC |
109 | } |
110 | ||
bd607166 KR |
111 | int amdgpu_fru_get_product_info(struct amdgpu_device *adev) |
112 | { | |
8a2b5139 | 113 | struct amdgpu_fru_info *fru_info; |
0dbf2c56 LT |
114 | unsigned char buf[8], *pia; |
115 | u32 addr, fru_addr; | |
ccdfbfec | 116 | int size, len; |
0dbf2c56 | 117 | u8 csum; |
bd607166 | 118 | |
afbe5d1e | 119 | if (!is_fru_eeprom_supported(adev, &fru_addr)) |
fabe01d7 JC |
120 | return 0; |
121 | ||
8a2b5139 LL |
122 | if (!adev->fru_info) { |
123 | adev->fru_info = kzalloc(sizeof(*adev->fru_info), GFP_KERNEL); | |
124 | if (!adev->fru_info) | |
125 | return -ENOMEM; | |
126 | } | |
127 | ||
128 | fru_info = adev->fru_info; | |
129 | /* For Arcturus-and-later, default value of serial_number is unique_id | |
130 | * so convert it to a 16-digit HEX string for convenience and | |
131 | * backwards-compatibility. | |
132 | */ | |
133 | sprintf(fru_info->serial, "%llx", adev->unique_id); | |
134 | ||
bd607166 | 135 | /* If algo exists, it means that the i2c_adapter's initialized */ |
2f60dd50 | 136 | if (!adev->pm.fru_eeprom_i2c_bus || !adev->pm.fru_eeprom_i2c_bus->algo) { |
bd607166 | 137 | DRM_WARN("Cannot access FRU, EEPROM accessor not initialized"); |
02b865f8 | 138 | return -ENODEV; |
bd607166 KR |
139 | } |
140 | ||
0dbf2c56 LT |
141 | /* Read the IPMI Common header */ |
142 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, fru_addr, buf, | |
143 | sizeof(buf)); | |
144 | if (len != 8) { | |
145 | DRM_ERROR("Couldn't read the IPMI Common Header: %d", len); | |
146 | return len < 0 ? len : -EIO; | |
bd607166 KR |
147 | } |
148 | ||
0dbf2c56 LT |
149 | if (buf[0] != 1) { |
150 | DRM_ERROR("Bad IPMI Common Header version: 0x%02x", buf[0]); | |
151 | return -EIO; | |
bd607166 KR |
152 | } |
153 | ||
0dbf2c56 LT |
154 | for (csum = 0; len > 0; len--) |
155 | csum += buf[len - 1]; | |
156 | if (csum) { | |
157 | DRM_ERROR("Bad IPMI Common Header checksum: 0x%02x", csum); | |
158 | return -EIO; | |
bd607166 KR |
159 | } |
160 | ||
0dbf2c56 LT |
161 | /* Get the offset to the Product Info Area (PIA). */ |
162 | addr = buf[4] * 8; | |
163 | if (!addr) | |
164 | return 0; | |
bd607166 | 165 | |
0dbf2c56 LT |
166 | /* Get the absolute address to the PIA. */ |
167 | addr += fru_addr; | |
bd607166 | 168 | |
0dbf2c56 LT |
169 | /* Read the header of the PIA. */ |
170 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, buf, 3); | |
171 | if (len != 3) { | |
172 | DRM_ERROR("Couldn't read the Product Info Area header: %d", len); | |
173 | return len < 0 ? len : -EIO; | |
bd607166 KR |
174 | } |
175 | ||
0dbf2c56 LT |
176 | if (buf[0] != 1) { |
177 | DRM_ERROR("Bad IPMI Product Info Area version: 0x%02x", buf[0]); | |
178 | return -EIO; | |
179 | } | |
bd607166 | 180 | |
0dbf2c56 LT |
181 | size = buf[1] * 8; |
182 | pia = kzalloc(size, GFP_KERNEL); | |
183 | if (!pia) | |
184 | return -ENOMEM; | |
185 | ||
186 | /* Read the whole PIA. */ | |
187 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, pia, size); | |
188 | if (len != size) { | |
189 | kfree(pia); | |
190 | DRM_ERROR("Couldn't read the Product Info Area: %d", len); | |
191 | return len < 0 ? len : -EIO; | |
bd607166 KR |
192 | } |
193 | ||
0dbf2c56 LT |
194 | for (csum = 0; size > 0; size--) |
195 | csum += pia[size - 1]; | |
196 | if (csum) { | |
197 | DRM_ERROR("Bad Product Info Area checksum: 0x%02x", csum); | |
9ed630c5 | 198 | kfree(pia); |
0dbf2c56 | 199 | return -EIO; |
714309f0 | 200 | } |
bd607166 | 201 | |
0dbf2c56 LT |
202 | /* Now extract useful information from the PIA. |
203 | * | |
ac6b1f27 | 204 | * Read Manufacturer Name field whose length is [3]. |
0dbf2c56 | 205 | */ |
ac6b1f27 LL |
206 | addr = 3; |
207 | if (addr + 1 >= len) | |
208 | goto Out; | |
209 | memcpy(fru_info->manufacturer_name, pia + addr + 1, | |
210 | min_t(size_t, sizeof(fru_info->manufacturer_name), | |
211 | pia[addr] & 0x3F)); | |
212 | fru_info->manufacturer_name[sizeof(fru_info->manufacturer_name) - 1] = | |
213 | '\0'; | |
214 | ||
215 | /* Read Product Name field. */ | |
216 | addr += 1 + (pia[addr] & 0x3F); | |
0dbf2c56 LT |
217 | if (addr + 1 >= len) |
218 | goto Out; | |
8a2b5139 LL |
219 | memcpy(fru_info->product_name, pia + addr + 1, |
220 | min_t(size_t, sizeof(fru_info->product_name), pia[addr] & 0x3F)); | |
221 | fru_info->product_name[sizeof(fru_info->product_name) - 1] = '\0'; | |
0dbf2c56 LT |
222 | |
223 | /* Go to the Product Part/Model Number field. */ | |
224 | addr += 1 + (pia[addr] & 0x3F); | |
225 | if (addr + 1 >= len) | |
226 | goto Out; | |
8a2b5139 LL |
227 | memcpy(fru_info->product_number, pia + addr + 1, |
228 | min_t(size_t, sizeof(fru_info->product_number), | |
0dbf2c56 | 229 | pia[addr] & 0x3F)); |
8a2b5139 | 230 | fru_info->product_number[sizeof(fru_info->product_number) - 1] = '\0'; |
0dbf2c56 LT |
231 | |
232 | /* Go to the Product Version field. */ | |
233 | addr += 1 + (pia[addr] & 0x3F); | |
234 | ||
235 | /* Go to the Product Serial Number field. */ | |
236 | addr += 1 + (pia[addr] & 0x3F); | |
237 | if (addr + 1 >= len) | |
238 | goto Out; | |
8a2b5139 LL |
239 | memcpy(fru_info->serial, pia + addr + 1, |
240 | min_t(size_t, sizeof(fru_info->serial), pia[addr] & 0x3F)); | |
241 | fru_info->serial[sizeof(fru_info->serial) - 1] = '\0'; | |
ac6b1f27 LL |
242 | |
243 | /* Asset Tag field */ | |
244 | addr += 1 + (pia[addr] & 0x3F); | |
245 | ||
246 | /* FRU File Id field. This could be 'null'. */ | |
247 | addr += 1 + (pia[addr] & 0x3F); | |
248 | if ((addr + 1 >= len) || !(pia[addr] & 0x3F)) | |
249 | goto Out; | |
250 | memcpy(fru_info->fru_id, pia + addr + 1, | |
251 | min_t(size_t, sizeof(fru_info->fru_id), pia[addr] & 0x3F)); | |
252 | fru_info->fru_id[sizeof(fru_info->fru_id) - 1] = '\0'; | |
253 | ||
0dbf2c56 LT |
254 | Out: |
255 | kfree(pia); | |
bd607166 KR |
256 | return 0; |
257 | } | |
7957ec80 LL |
258 | |
259 | /** | |
260 | * DOC: product_name | |
261 | * | |
262 | * The amdgpu driver provides a sysfs API for reporting the product name | |
263 | * for the device | |
264 | * The file product_name is used for this and returns the product name | |
265 | * as returned from the FRU. | |
266 | * NOTE: This is only available for certain server cards | |
267 | */ | |
268 | ||
269 | static ssize_t amdgpu_fru_product_name_show(struct device *dev, | |
270 | struct device_attribute *attr, | |
271 | char *buf) | |
272 | { | |
273 | struct drm_device *ddev = dev_get_drvdata(dev); | |
274 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
275 | ||
8a2b5139 | 276 | return sysfs_emit(buf, "%s\n", adev->fru_info->product_name); |
7957ec80 LL |
277 | } |
278 | ||
279 | static DEVICE_ATTR(product_name, 0444, amdgpu_fru_product_name_show, NULL); | |
280 | ||
281 | /** | |
282 | * DOC: product_number | |
283 | * | |
284 | * The amdgpu driver provides a sysfs API for reporting the part number | |
285 | * for the device | |
286 | * The file product_number is used for this and returns the part number | |
287 | * as returned from the FRU. | |
288 | * NOTE: This is only available for certain server cards | |
289 | */ | |
290 | ||
291 | static ssize_t amdgpu_fru_product_number_show(struct device *dev, | |
292 | struct device_attribute *attr, | |
293 | char *buf) | |
294 | { | |
295 | struct drm_device *ddev = dev_get_drvdata(dev); | |
296 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
297 | ||
8a2b5139 | 298 | return sysfs_emit(buf, "%s\n", adev->fru_info->product_number); |
7957ec80 LL |
299 | } |
300 | ||
301 | static DEVICE_ATTR(product_number, 0444, amdgpu_fru_product_number_show, NULL); | |
302 | ||
303 | /** | |
304 | * DOC: serial_number | |
305 | * | |
306 | * The amdgpu driver provides a sysfs API for reporting the serial number | |
307 | * for the device | |
308 | * The file serial_number is used for this and returns the serial number | |
309 | * as returned from the FRU. | |
310 | * NOTE: This is only available for certain server cards | |
311 | */ | |
312 | ||
313 | static ssize_t amdgpu_fru_serial_number_show(struct device *dev, | |
314 | struct device_attribute *attr, | |
315 | char *buf) | |
316 | { | |
317 | struct drm_device *ddev = dev_get_drvdata(dev); | |
318 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
319 | ||
8a2b5139 | 320 | return sysfs_emit(buf, "%s\n", adev->fru_info->serial); |
7957ec80 LL |
321 | } |
322 | ||
323 | static DEVICE_ATTR(serial_number, 0444, amdgpu_fru_serial_number_show, NULL); | |
324 | ||
b3e73b5a LL |
325 | /** |
326 | * DOC: fru_id | |
327 | * | |
328 | * The amdgpu driver provides a sysfs API for reporting FRU File Id | |
329 | * for the device. | |
330 | * The file fru_id is used for this and returns the File Id value | |
331 | * as returned from the FRU. | |
332 | * NOTE: This is only available for certain server cards | |
333 | */ | |
334 | ||
ac6b1f27 LL |
335 | static ssize_t amdgpu_fru_id_show(struct device *dev, |
336 | struct device_attribute *attr, char *buf) | |
337 | { | |
338 | struct drm_device *ddev = dev_get_drvdata(dev); | |
339 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
340 | ||
341 | return sysfs_emit(buf, "%s\n", adev->fru_info->fru_id); | |
342 | } | |
343 | ||
344 | static DEVICE_ATTR(fru_id, 0444, amdgpu_fru_id_show, NULL); | |
345 | ||
b3e73b5a LL |
346 | /** |
347 | * DOC: manufacturer | |
348 | * | |
349 | * The amdgpu driver provides a sysfs API for reporting manufacturer name from | |
350 | * FRU information. | |
351 | * The file manufacturer returns the value as returned from the FRU. | |
352 | * NOTE: This is only available for certain server cards | |
353 | */ | |
354 | ||
ac6b1f27 LL |
355 | static ssize_t amdgpu_fru_manufacturer_name_show(struct device *dev, |
356 | struct device_attribute *attr, | |
357 | char *buf) | |
358 | { | |
359 | struct drm_device *ddev = dev_get_drvdata(dev); | |
360 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
361 | ||
362 | return sysfs_emit(buf, "%s\n", adev->fru_info->manufacturer_name); | |
363 | } | |
364 | ||
365 | static DEVICE_ATTR(manufacturer, 0444, amdgpu_fru_manufacturer_name_show, NULL); | |
366 | ||
7957ec80 LL |
367 | static const struct attribute *amdgpu_fru_attributes[] = { |
368 | &dev_attr_product_name.attr, | |
369 | &dev_attr_product_number.attr, | |
370 | &dev_attr_serial_number.attr, | |
ac6b1f27 LL |
371 | &dev_attr_fru_id.attr, |
372 | &dev_attr_manufacturer.attr, | |
7957ec80 LL |
373 | NULL |
374 | }; | |
375 | ||
376 | int amdgpu_fru_sysfs_init(struct amdgpu_device *adev) | |
377 | { | |
8a2b5139 | 378 | if (!is_fru_eeprom_supported(adev, NULL) || !adev->fru_info) |
7957ec80 LL |
379 | return 0; |
380 | ||
381 | return sysfs_create_files(&adev->dev->kobj, amdgpu_fru_attributes); | |
382 | } | |
383 | ||
384 | void amdgpu_fru_sysfs_fini(struct amdgpu_device *adev) | |
385 | { | |
8a2b5139 | 386 | if (!is_fru_eeprom_supported(adev, NULL) || !adev->fru_info) |
7957ec80 LL |
387 | return; |
388 | ||
389 | sysfs_remove_files(&adev->dev->kobj, amdgpu_fru_attributes); | |
390 | } |