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 | |
45 | */ | |
46 | if (amdgpu_sriov_vf(adev)) | |
47 | return false; | |
48 | ||
afbe5d1e LT |
49 | /* The default I2C EEPROM address of the FRU. |
50 | */ | |
51 | if (fru_addr) | |
52 | *fru_addr = FRU_EEPROM_MADDR_8; | |
53 | ||
3ed89339 | 54 | /* VBIOS is of the format ###-DXXXYYYY-##. For SKU identification, |
f94582e4 KR |
55 | * we can use just the "DXXX" portion. If there were more models, we |
56 | * could convert the 3 characters to a hex integer and use a switch | |
57 | * for ease/speed/readability. For now, 2 string comparisons are | |
58 | * reasonable and not too expensive | |
59 | */ | |
60 | switch (adev->asic_type) { | |
61 | case CHIP_VEGA20: | |
62 | /* D161 and D163 are the VG20 server SKUs */ | |
adf64e21 ML |
63 | if (strnstr(atom_ctx->vbios_pn, "D161", |
64 | sizeof(atom_ctx->vbios_pn)) || | |
65 | strnstr(atom_ctx->vbios_pn, "D163", | |
66 | sizeof(atom_ctx->vbios_pn))) { | |
28afcb0a LT |
67 | if (fru_addr) |
68 | *fru_addr = FRU_EEPROM_MADDR_6; | |
f94582e4 | 69 | return true; |
afbe5d1e | 70 | } else { |
f94582e4 | 71 | return false; |
afbe5d1e | 72 | } |
67416bf8 | 73 | case CHIP_ALDEBARAN: |
afbe5d1e | 74 | /* All Aldebaran SKUs have an FRU */ |
adf64e21 ML |
75 | if (!strnstr(atom_ctx->vbios_pn, "D673", |
76 | sizeof(atom_ctx->vbios_pn))) | |
afbe5d1e LT |
77 | if (fru_addr) |
78 | *fru_addr = FRU_EEPROM_MADDR_6; | |
67416bf8 | 79 | return true; |
3ed89339 | 80 | case CHIP_SIENNA_CICHLID: |
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 | } |
f94582e4 KR |
95 | default: |
96 | return false; | |
97 | } | |
fabe01d7 JC |
98 | } |
99 | ||
bd607166 KR |
100 | int amdgpu_fru_get_product_info(struct amdgpu_device *adev) |
101 | { | |
0dbf2c56 LT |
102 | unsigned char buf[8], *pia; |
103 | u32 addr, fru_addr; | |
ccdfbfec | 104 | int size, len; |
0dbf2c56 | 105 | u8 csum; |
bd607166 | 106 | |
afbe5d1e | 107 | if (!is_fru_eeprom_supported(adev, &fru_addr)) |
fabe01d7 JC |
108 | return 0; |
109 | ||
bd607166 | 110 | /* If algo exists, it means that the i2c_adapter's initialized */ |
2f60dd50 | 111 | if (!adev->pm.fru_eeprom_i2c_bus || !adev->pm.fru_eeprom_i2c_bus->algo) { |
bd607166 | 112 | DRM_WARN("Cannot access FRU, EEPROM accessor not initialized"); |
02b865f8 | 113 | return -ENODEV; |
bd607166 KR |
114 | } |
115 | ||
0dbf2c56 LT |
116 | /* Read the IPMI Common header */ |
117 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, fru_addr, buf, | |
118 | sizeof(buf)); | |
119 | if (len != 8) { | |
120 | DRM_ERROR("Couldn't read the IPMI Common Header: %d", len); | |
121 | return len < 0 ? len : -EIO; | |
bd607166 KR |
122 | } |
123 | ||
0dbf2c56 LT |
124 | if (buf[0] != 1) { |
125 | DRM_ERROR("Bad IPMI Common Header version: 0x%02x", buf[0]); | |
126 | return -EIO; | |
bd607166 KR |
127 | } |
128 | ||
0dbf2c56 LT |
129 | for (csum = 0; len > 0; len--) |
130 | csum += buf[len - 1]; | |
131 | if (csum) { | |
132 | DRM_ERROR("Bad IPMI Common Header checksum: 0x%02x", csum); | |
133 | return -EIO; | |
bd607166 KR |
134 | } |
135 | ||
0dbf2c56 LT |
136 | /* Get the offset to the Product Info Area (PIA). */ |
137 | addr = buf[4] * 8; | |
138 | if (!addr) | |
139 | return 0; | |
bd607166 | 140 | |
0dbf2c56 LT |
141 | /* Get the absolute address to the PIA. */ |
142 | addr += fru_addr; | |
bd607166 | 143 | |
0dbf2c56 LT |
144 | /* Read the header of the PIA. */ |
145 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, buf, 3); | |
146 | if (len != 3) { | |
147 | DRM_ERROR("Couldn't read the Product Info Area header: %d", len); | |
148 | return len < 0 ? len : -EIO; | |
bd607166 KR |
149 | } |
150 | ||
0dbf2c56 LT |
151 | if (buf[0] != 1) { |
152 | DRM_ERROR("Bad IPMI Product Info Area version: 0x%02x", buf[0]); | |
153 | return -EIO; | |
154 | } | |
bd607166 | 155 | |
0dbf2c56 LT |
156 | size = buf[1] * 8; |
157 | pia = kzalloc(size, GFP_KERNEL); | |
158 | if (!pia) | |
159 | return -ENOMEM; | |
160 | ||
161 | /* Read the whole PIA. */ | |
162 | len = amdgpu_eeprom_read(adev->pm.fru_eeprom_i2c_bus, addr, pia, size); | |
163 | if (len != size) { | |
164 | kfree(pia); | |
165 | DRM_ERROR("Couldn't read the Product Info Area: %d", len); | |
166 | return len < 0 ? len : -EIO; | |
bd607166 KR |
167 | } |
168 | ||
0dbf2c56 LT |
169 | for (csum = 0; size > 0; size--) |
170 | csum += pia[size - 1]; | |
171 | if (csum) { | |
172 | DRM_ERROR("Bad Product Info Area checksum: 0x%02x", csum); | |
5d061675 | 173 | kfree(pia); |
0dbf2c56 | 174 | return -EIO; |
714309f0 | 175 | } |
bd607166 | 176 | |
0dbf2c56 LT |
177 | /* Now extract useful information from the PIA. |
178 | * | |
179 | * Skip the Manufacturer Name at [3] and go directly to | |
180 | * the Product Name field. | |
181 | */ | |
182 | addr = 3 + 1 + (pia[3] & 0x3F); | |
183 | if (addr + 1 >= len) | |
184 | goto Out; | |
185 | memcpy(adev->product_name, pia + addr + 1, | |
186 | min_t(size_t, | |
187 | sizeof(adev->product_name), | |
188 | pia[addr] & 0x3F)); | |
189 | adev->product_name[sizeof(adev->product_name) - 1] = '\0'; | |
190 | ||
191 | /* Go to the Product Part/Model Number field. */ | |
192 | addr += 1 + (pia[addr] & 0x3F); | |
193 | if (addr + 1 >= len) | |
194 | goto Out; | |
195 | memcpy(adev->product_number, pia + addr + 1, | |
196 | min_t(size_t, | |
197 | sizeof(adev->product_number), | |
198 | pia[addr] & 0x3F)); | |
199 | adev->product_number[sizeof(adev->product_number) - 1] = '\0'; | |
200 | ||
201 | /* Go to the Product Version field. */ | |
202 | addr += 1 + (pia[addr] & 0x3F); | |
203 | ||
204 | /* Go to the Product Serial Number field. */ | |
205 | addr += 1 + (pia[addr] & 0x3F); | |
206 | if (addr + 1 >= len) | |
207 | goto Out; | |
208 | memcpy(adev->serial, pia + addr + 1, min_t(size_t, | |
209 | sizeof(adev->serial), | |
210 | pia[addr] & 0x3F)); | |
211 | adev->serial[sizeof(adev->serial) - 1] = '\0'; | |
212 | Out: | |
213 | kfree(pia); | |
bd607166 KR |
214 | return 0; |
215 | } | |
7957ec80 LL |
216 | |
217 | /** | |
218 | * DOC: product_name | |
219 | * | |
220 | * The amdgpu driver provides a sysfs API for reporting the product name | |
221 | * for the device | |
222 | * The file product_name is used for this and returns the product name | |
223 | * as returned from the FRU. | |
224 | * NOTE: This is only available for certain server cards | |
225 | */ | |
226 | ||
227 | static ssize_t amdgpu_fru_product_name_show(struct device *dev, | |
228 | struct device_attribute *attr, | |
229 | char *buf) | |
230 | { | |
231 | struct drm_device *ddev = dev_get_drvdata(dev); | |
232 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
233 | ||
234 | return sysfs_emit(buf, "%s\n", adev->product_name); | |
235 | } | |
236 | ||
237 | static DEVICE_ATTR(product_name, 0444, amdgpu_fru_product_name_show, NULL); | |
238 | ||
239 | /** | |
240 | * DOC: product_number | |
241 | * | |
242 | * The amdgpu driver provides a sysfs API for reporting the part number | |
243 | * for the device | |
244 | * The file product_number is used for this and returns the part number | |
245 | * as returned from the FRU. | |
246 | * NOTE: This is only available for certain server cards | |
247 | */ | |
248 | ||
249 | static ssize_t amdgpu_fru_product_number_show(struct device *dev, | |
250 | struct device_attribute *attr, | |
251 | char *buf) | |
252 | { | |
253 | struct drm_device *ddev = dev_get_drvdata(dev); | |
254 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
255 | ||
256 | return sysfs_emit(buf, "%s\n", adev->product_number); | |
257 | } | |
258 | ||
259 | static DEVICE_ATTR(product_number, 0444, amdgpu_fru_product_number_show, NULL); | |
260 | ||
261 | /** | |
262 | * DOC: serial_number | |
263 | * | |
264 | * The amdgpu driver provides a sysfs API for reporting the serial number | |
265 | * for the device | |
266 | * The file serial_number is used for this and returns the serial number | |
267 | * as returned from the FRU. | |
268 | * NOTE: This is only available for certain server cards | |
269 | */ | |
270 | ||
271 | static ssize_t amdgpu_fru_serial_number_show(struct device *dev, | |
272 | struct device_attribute *attr, | |
273 | char *buf) | |
274 | { | |
275 | struct drm_device *ddev = dev_get_drvdata(dev); | |
276 | struct amdgpu_device *adev = drm_to_adev(ddev); | |
277 | ||
278 | return sysfs_emit(buf, "%s\n", adev->serial); | |
279 | } | |
280 | ||
281 | static DEVICE_ATTR(serial_number, 0444, amdgpu_fru_serial_number_show, NULL); | |
282 | ||
283 | static const struct attribute *amdgpu_fru_attributes[] = { | |
284 | &dev_attr_product_name.attr, | |
285 | &dev_attr_product_number.attr, | |
286 | &dev_attr_serial_number.attr, | |
287 | NULL | |
288 | }; | |
289 | ||
290 | int amdgpu_fru_sysfs_init(struct amdgpu_device *adev) | |
291 | { | |
292 | if (!is_fru_eeprom_supported(adev, NULL)) | |
293 | return 0; | |
294 | ||
295 | return sysfs_create_files(&adev->dev->kobj, amdgpu_fru_attributes); | |
296 | } | |
297 | ||
298 | void amdgpu_fru_sysfs_fini(struct amdgpu_device *adev) | |
299 | { | |
300 | if (!is_fru_eeprom_supported(adev, NULL)) | |
301 | return; | |
302 | ||
303 | sysfs_remove_files(&adev->dev->kobj, amdgpu_fru_attributes); | |
304 | } |