Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hwmon: Add driver for Astera Labs PT5161L retimer

This driver implements support for temperature monitoring of Astera Labs
PT5161L series PCIe retimer chips.

This driver implementation originates from the CSDK available at
Link: https://github.com/facebook/openbmc/tree/helium/common/recipes-lib/retimer-v2.14
The communication protocol utilized is based on the I2C/SMBus standard.

Signed-off-by: Cosmo Chou <chou.cosmo@gmail.com>
Link: https://lore.kernel.org/r/20240206125420.3884300-2-chou.cosmo@gmail.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

Cosmo Chou and committed by
Guenter Roeck
1b2ca93c 1a793caf

+728
+1
Documentation/hwmon/index.rst
··· 190 190 pmbus 191 191 powerz 192 192 powr1220 193 + pt5161l 193 194 pxe1610 194 195 pwm-fan 195 196 q54sj108a2
+42
Documentation/hwmon/pt5161l.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + Kernel driver pt5161l 4 + ===================== 5 + 6 + Supported chips: 7 + 8 + * Astera Labs PT5161L 9 + 10 + Prefix: 'pt5161l' 11 + 12 + Addresses scanned: I2C 0x20 - 0x27 13 + 14 + Datasheet: Not publicly available. 15 + 16 + Authors: Cosmo Chou <cosmo.chou@quantatw.com> 17 + 18 + Description 19 + ----------- 20 + 21 + This driver implements support for temperature monitoring of Astera Labs 22 + PT5161L series PCIe retimer chips. 23 + 24 + This driver implementation originates from the CSDK available at 25 + https://github.com/facebook/openbmc/tree/helium/common/recipes-lib/retimer-v2.14 26 + The communication protocol utilized is based on the I2C/SMBus standard. 27 + 28 + Sysfs entries 29 + ---------------- 30 + 31 + ================ ============================================== 32 + temp1_input Measured temperature (in millidegrees Celsius) 33 + ================ ============================================== 34 + 35 + Debugfs entries 36 + ---------------- 37 + 38 + ================ =============================== 39 + fw_load_status Firmware load status 40 + fw_ver Firmware version of the retimer 41 + heartbeat_status Heartbeat status 42 + ================ ===============================
+7
MAINTAINERS
··· 17698 17698 F: include/linux/pstore* 17699 17699 K: \b(pstore|ramoops) 17700 17700 17701 + PT5161L HARDWARE MONITOR DRIVER 17702 + M: Cosmo Chou <cosmo.chou@quantatw.com> 17703 + L: linux-hwmon@vger.kernel.org 17704 + S: Maintained 17705 + F: Documentation/hwmon/pt5161l.rst 17706 + F: drivers/hwmon/pt5161l.c 17707 + 17701 17708 PTP HARDWARE CLOCK SUPPORT 17702 17709 M: Richard Cochran <richardcochran@gmail.com> 17703 17710 L: netdev@vger.kernel.org
+10
drivers/hwmon/Kconfig
··· 1755 1755 1756 1756 source "drivers/hwmon/pmbus/Kconfig" 1757 1757 1758 + config SENSORS_PT5161L 1759 + tristate "Astera Labs PT5161L PCIe retimer hardware monitoring" 1760 + depends on I2C 1761 + help 1762 + If you say yes here you get support for temperature monitoring 1763 + on the Astera Labs PT5161L PCIe retimer. 1764 + 1765 + This driver can also be built as a module. If so, the module 1766 + will be called pt5161l. 1767 + 1758 1768 config SENSORS_PWM_FAN 1759 1769 tristate "PWM fan" 1760 1770 depends on (PWM && OF) || COMPILE_TEST
+1
drivers/hwmon/Makefile
··· 184 184 obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o 185 185 obj-$(CONFIG_SENSORS_POWERZ) += powerz.o 186 186 obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o 187 + obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o 187 188 obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o 188 189 obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o 189 190 obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
+667
drivers/hwmon/pt5161l.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + #include <linux/debugfs.h> 4 + #include <linux/delay.h> 5 + #include <linux/err.h> 6 + #include <linux/i2c.h> 7 + #include <linux/init.h> 8 + #include <linux/hwmon.h> 9 + #include <linux/module.h> 10 + #include <linux/mutex.h> 11 + 12 + /* Aries current average temp ADC code CSR */ 13 + #define ARIES_CURRENT_AVG_TEMP_ADC_CSR 0x42c 14 + 15 + /* Device Load check register */ 16 + #define ARIES_CODE_LOAD_REG 0x605 17 + /* Value indicating FW was loaded properly, [3:1] = 3'b111 */ 18 + #define ARIES_LOAD_CODE 0xe 19 + 20 + /* Main Micro Heartbeat register */ 21 + #define ARIES_MM_HEARTBEAT_ADDR 0x923 22 + 23 + /* Reg offset to specify Address for MM assisted accesses */ 24 + #define ARIES_MM_ASSIST_REG_ADDR_OFFSET 0xd99 25 + /* Reg offset to specify Command for MM assisted accesses */ 26 + #define ARIES_MM_ASSIST_CMD_OFFSET 0xd9d 27 + /* Reg offset to MM SPARE 0 used specify Address[7:0] */ 28 + #define ARIES_MM_ASSIST_SPARE_0_OFFSET 0xd9f 29 + /* Reg offset to MM SPARE 3 used specify Data Byte 0 */ 30 + #define ARIES_MM_ASSIST_SPARE_3_OFFSET 0xda2 31 + /* Wide register reads */ 32 + #define ARIES_MM_RD_WIDE_REG_2B 0x1d 33 + #define ARIES_MM_RD_WIDE_REG_3B 0x1e 34 + #define ARIES_MM_RD_WIDE_REG_4B 0x1f 35 + #define ARIES_MM_RD_WIDE_REG_5B 0x20 36 + 37 + /* Time delay between checking MM status of EEPROM write (microseconds) */ 38 + #define ARIES_MM_STATUS_TIME 5000 39 + 40 + /* AL Main SRAM DMEM offset (A0) */ 41 + #define AL_MAIN_SRAM_DMEM_OFFSET (64 * 1024) 42 + /* SRAM read command */ 43 + #define AL_TG_RD_LOC_IND_SRAM 0x16 44 + 45 + /* Offset for main micro FW info */ 46 + #define ARIES_MAIN_MICRO_FW_INFO (96 * 1024 - 128) 47 + /* FW Info (Major) offset location in struct */ 48 + #define ARIES_MM_FW_VERSION_MAJOR 0 49 + /* FW Info (Minor) offset location in struct */ 50 + #define ARIES_MM_FW_VERSION_MINOR 1 51 + /* FW Info (Build no.) offset location in struct */ 52 + #define ARIES_MM_FW_VERSION_BUILD 2 53 + 54 + #define ARIES_TEMP_CAL_CODE_DEFAULT 84 55 + 56 + /* Struct defining FW version loaded on an Aries device */ 57 + struct pt5161l_fw_ver { 58 + u8 major; 59 + u8 minor; 60 + u16 build; 61 + }; 62 + 63 + /* Each client has this additional data */ 64 + struct pt5161l_data { 65 + struct i2c_client *client; 66 + struct dentry *debugfs; 67 + struct pt5161l_fw_ver fw_ver; 68 + struct mutex lock; /* for atomic I2C transactions */ 69 + bool init_done; 70 + bool code_load_okay; /* indicate if code load reg value is expected */ 71 + bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */ 72 + bool mm_wide_reg_access; /* MM assisted wide register access */ 73 + }; 74 + 75 + static struct dentry *pt5161l_debugfs_dir; 76 + 77 + /* 78 + * Write multiple data bytes to Aries over I2C 79 + */ 80 + static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address, 81 + u8 len, u8 *val) 82 + { 83 + struct i2c_client *client = data->client; 84 + int ret; 85 + u8 remain_len = len; 86 + u8 xfer_len, curr_len; 87 + u8 buf[16]; 88 + u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ 89 + u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ 90 + 91 + while (remain_len > 0) { 92 + if (remain_len > 4) { 93 + curr_len = 4; 94 + remain_len -= 4; 95 + } else { 96 + curr_len = remain_len; 97 + remain_len = 0; 98 + } 99 + 100 + buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1); 101 + buf[1] = (address >> 8) & 0xff; 102 + buf[2] = address & 0xff; 103 + memcpy(&buf[3], val, curr_len); 104 + 105 + xfer_len = 3 + curr_len; 106 + ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf); 107 + if (ret) 108 + return ret; 109 + 110 + val += curr_len; 111 + address += curr_len; 112 + } 113 + 114 + return 0; 115 + } 116 + 117 + /* 118 + * Read multiple data bytes from Aries over I2C 119 + */ 120 + static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, 121 + u8 len, u8 *val) 122 + { 123 + struct i2c_client *client = data->client; 124 + int ret, tries; 125 + u8 remain_len = len; 126 + u8 curr_len; 127 + u8 wbuf[16], rbuf[24]; 128 + u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ 129 + u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ 130 + 131 + while (remain_len > 0) { 132 + if (remain_len > 16) { 133 + curr_len = 16; 134 + remain_len -= 16; 135 + } else { 136 + curr_len = remain_len; 137 + remain_len = 0; 138 + } 139 + 140 + wbuf[0] = config | (curr_len - 1) << 1 | 141 + ((address >> 16) & 0x1); 142 + wbuf[1] = (address >> 8) & 0xff; 143 + wbuf[2] = address & 0xff; 144 + 145 + for (tries = 0; tries < 3; tries++) { 146 + ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3, 147 + wbuf); 148 + if (ret) 149 + return ret; 150 + 151 + ret = i2c_smbus_read_block_data(client, (cmd | 0x1), 152 + rbuf); 153 + if (ret == curr_len) 154 + break; 155 + } 156 + if (tries >= 3) 157 + return ret; 158 + 159 + memcpy(val, rbuf, curr_len); 160 + val += curr_len; 161 + address += curr_len; 162 + } 163 + 164 + return 0; 165 + } 166 + 167 + static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address, 168 + u8 width, u8 *val) 169 + { 170 + int ret, tries; 171 + u8 buf[8]; 172 + u8 status; 173 + 174 + /* 175 + * Safely access wide registers using mailbox method to prevent 176 + * risking conflict with Aries firmware; otherwise fallback to 177 + * legacy, less secure method. 178 + */ 179 + if (data->mm_wide_reg_access) { 180 + buf[0] = address & 0xff; 181 + buf[1] = (address >> 8) & 0xff; 182 + buf[2] = (address >> 16) & 0x1; 183 + ret = pt5161l_write_block_data(data, 184 + ARIES_MM_ASSIST_SPARE_0_OFFSET, 185 + 3, buf); 186 + if (ret) 187 + return ret; 188 + 189 + /* Set command based on width */ 190 + switch (width) { 191 + case 2: 192 + buf[0] = ARIES_MM_RD_WIDE_REG_2B; 193 + break; 194 + case 3: 195 + buf[0] = ARIES_MM_RD_WIDE_REG_3B; 196 + break; 197 + case 4: 198 + buf[0] = ARIES_MM_RD_WIDE_REG_4B; 199 + break; 200 + case 5: 201 + buf[0] = ARIES_MM_RD_WIDE_REG_5B; 202 + break; 203 + default: 204 + return -EINVAL; 205 + } 206 + ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET, 207 + 1, buf); 208 + if (ret) 209 + return ret; 210 + 211 + status = 0xff; 212 + for (tries = 0; tries < 100; tries++) { 213 + ret = pt5161l_read_block_data(data, 214 + ARIES_MM_ASSIST_CMD_OFFSET, 215 + 1, &status); 216 + if (ret) 217 + return ret; 218 + 219 + if (status == 0) 220 + break; 221 + 222 + usleep_range(ARIES_MM_STATUS_TIME, 223 + ARIES_MM_STATUS_TIME + 1000); 224 + } 225 + if (status != 0) 226 + return -ETIMEDOUT; 227 + 228 + ret = pt5161l_read_block_data(data, 229 + ARIES_MM_ASSIST_SPARE_3_OFFSET, 230 + width, val); 231 + if (ret) 232 + return ret; 233 + } else { 234 + return pt5161l_read_block_data(data, address, width, val); 235 + } 236 + 237 + return 0; 238 + } 239 + 240 + /* 241 + * Read multiple (up to eight) data bytes from micro SRAM over I2C 242 + */ 243 + static int 244 + pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data, 245 + u32 address, u8 len, u8 *val) 246 + { 247 + int ret, tries; 248 + u8 buf[8]; 249 + u8 i, status; 250 + u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET; 251 + u32 eeprom_base, eeprom_addr; 252 + 253 + /* No multi-byte indirect support here. Hence read a byte at a time */ 254 + eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET; 255 + for (i = 0; i < len; i++) { 256 + eeprom_addr = eeprom_base + i; 257 + buf[0] = eeprom_addr & 0xff; 258 + buf[1] = (eeprom_addr >> 8) & 0xff; 259 + buf[2] = (eeprom_addr >> 16) & 0xff; 260 + ret = pt5161l_write_block_data(data, uind_offs, 3, buf); 261 + if (ret) 262 + return ret; 263 + 264 + buf[0] = AL_TG_RD_LOC_IND_SRAM; 265 + ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf); 266 + if (ret) 267 + return ret; 268 + 269 + status = 0xff; 270 + for (tries = 0; tries < 255; tries++) { 271 + ret = pt5161l_read_block_data(data, uind_offs + 4, 1, 272 + &status); 273 + if (ret) 274 + return ret; 275 + 276 + if (status == 0) 277 + break; 278 + } 279 + if (status != 0) 280 + return -ETIMEDOUT; 281 + 282 + ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf); 283 + if (ret) 284 + return ret; 285 + 286 + val[i] = buf[0]; 287 + } 288 + 289 + return 0; 290 + } 291 + 292 + /* 293 + * Check firmware load status 294 + */ 295 + static int pt5161l_fw_load_check(struct pt5161l_data *data) 296 + { 297 + int ret; 298 + u8 buf[8]; 299 + 300 + ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf); 301 + if (ret) 302 + return ret; 303 + 304 + if (buf[0] < ARIES_LOAD_CODE) { 305 + dev_dbg(&data->client->dev, 306 + "Code Load reg unexpected. Not all modules are loaded %x\n", 307 + buf[0]); 308 + data->code_load_okay = false; 309 + } else { 310 + data->code_load_okay = true; 311 + } 312 + 313 + return 0; 314 + } 315 + 316 + /* 317 + * Check main micro heartbeat 318 + */ 319 + static int pt5161l_heartbeat_check(struct pt5161l_data *data) 320 + { 321 + int ret, tries; 322 + u8 buf[8]; 323 + u8 heartbeat; 324 + bool hb_changed = false; 325 + 326 + ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf); 327 + if (ret) 328 + return ret; 329 + 330 + heartbeat = buf[0]; 331 + for (tries = 0; tries < 100; tries++) { 332 + ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, 333 + buf); 334 + if (ret) 335 + return ret; 336 + 337 + if (buf[0] != heartbeat) { 338 + hb_changed = true; 339 + break; 340 + } 341 + } 342 + data->mm_heartbeat_okay = hb_changed; 343 + 344 + return 0; 345 + } 346 + 347 + /* 348 + * Check the status of firmware 349 + */ 350 + static int pt5161l_fwsts_check(struct pt5161l_data *data) 351 + { 352 + int ret; 353 + u8 buf[8]; 354 + u8 major = 0, minor = 0; 355 + u16 build = 0; 356 + 357 + ret = pt5161l_fw_load_check(data); 358 + if (ret) 359 + return ret; 360 + 361 + ret = pt5161l_heartbeat_check(data); 362 + if (ret) 363 + return ret; 364 + 365 + if (data->code_load_okay && data->mm_heartbeat_okay) { 366 + ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + 367 + ARIES_MM_FW_VERSION_MAJOR, 368 + 1, &major); 369 + if (ret) 370 + return ret; 371 + 372 + ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + 373 + ARIES_MM_FW_VERSION_MINOR, 374 + 1, &minor); 375 + if (ret) 376 + return ret; 377 + 378 + ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + 379 + ARIES_MM_FW_VERSION_BUILD, 380 + 2, buf); 381 + if (ret) 382 + return ret; 383 + build = buf[1] << 8 | buf[0]; 384 + } 385 + data->fw_ver.major = major; 386 + data->fw_ver.minor = minor; 387 + data->fw_ver.build = build; 388 + 389 + return 0; 390 + } 391 + 392 + static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor, 393 + u16 build) 394 + { 395 + u32 ver = major << 24 | minor << 16 | build; 396 + u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 | 397 + data->fw_ver.build; 398 + 399 + if (curr_ver >= ver) 400 + return true; 401 + 402 + return false; 403 + } 404 + 405 + static int pt5161l_init_dev(struct pt5161l_data *data) 406 + { 407 + int ret; 408 + 409 + mutex_lock(&data->lock); 410 + ret = pt5161l_fwsts_check(data); 411 + mutex_unlock(&data->lock); 412 + if (ret) 413 + return ret; 414 + 415 + /* Firmware 2.2.0 enables safe access to wide registers */ 416 + if (pt5161l_fw_is_at_least(data, 2, 2, 0)) 417 + data->mm_wide_reg_access = true; 418 + 419 + data->init_done = true; 420 + 421 + return 0; 422 + } 423 + 424 + static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type, 425 + u32 attr, int channel, long *val) 426 + { 427 + struct pt5161l_data *data = dev_get_drvdata(dev); 428 + int ret; 429 + u8 buf[8]; 430 + long adc_code; 431 + 432 + switch (attr) { 433 + case hwmon_temp_input: 434 + if (!data->init_done) { 435 + ret = pt5161l_init_dev(data); 436 + if (ret) 437 + return ret; 438 + } 439 + 440 + mutex_lock(&data->lock); 441 + ret = pt5161l_read_wide_reg(data, 442 + ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4, 443 + buf); 444 + mutex_unlock(&data->lock); 445 + if (ret) { 446 + dev_dbg(dev, "Read adc_code failed %d\n", ret); 447 + return ret; 448 + } 449 + 450 + adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; 451 + if (adc_code == 0 || adc_code >= 0x3ff) { 452 + dev_dbg(dev, "Invalid adc_code %lx\n", adc_code); 453 + return -EIO; 454 + } 455 + 456 + *val = 110000 + 457 + ((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) * 458 + -320); 459 + break; 460 + default: 461 + return -EOPNOTSUPP; 462 + } 463 + 464 + return 0; 465 + } 466 + 467 + static umode_t pt5161l_is_visible(const void *data, 468 + enum hwmon_sensor_types type, u32 attr, 469 + int channel) 470 + { 471 + switch (attr) { 472 + case hwmon_temp_input: 473 + return 0444; 474 + default: 475 + break; 476 + } 477 + 478 + return 0; 479 + } 480 + 481 + static const struct hwmon_channel_info *pt5161l_info[] = { 482 + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 483 + NULL 484 + }; 485 + 486 + static const struct hwmon_ops pt5161l_hwmon_ops = { 487 + .is_visible = pt5161l_is_visible, 488 + .read = pt5161l_read, 489 + }; 490 + 491 + static const struct hwmon_chip_info pt5161l_chip_info = { 492 + .ops = &pt5161l_hwmon_ops, 493 + .info = pt5161l_info, 494 + }; 495 + 496 + static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf, 497 + size_t count, loff_t *ppos) 498 + { 499 + struct pt5161l_data *data = file->private_data; 500 + int ret; 501 + char ver[32]; 502 + 503 + mutex_lock(&data->lock); 504 + ret = pt5161l_fwsts_check(data); 505 + mutex_unlock(&data->lock); 506 + if (ret) 507 + return ret; 508 + 509 + ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major, 510 + data->fw_ver.minor, data->fw_ver.build); 511 + 512 + return simple_read_from_buffer(buf, count, ppos, ver, ret); 513 + } 514 + 515 + static const struct file_operations pt5161l_debugfs_ops_fw_ver = { 516 + .read = pt5161l_debugfs_read_fw_ver, 517 + .open = simple_open, 518 + }; 519 + 520 + static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file, 521 + char __user *buf, size_t count, 522 + loff_t *ppos) 523 + { 524 + struct pt5161l_data *data = file->private_data; 525 + int ret; 526 + bool status = false; 527 + char health[16]; 528 + 529 + mutex_lock(&data->lock); 530 + ret = pt5161l_fw_load_check(data); 531 + mutex_unlock(&data->lock); 532 + if (ret == 0) 533 + status = data->code_load_okay; 534 + 535 + ret = snprintf(health, sizeof(health), "%s\n", 536 + status ? "normal" : "abnormal"); 537 + 538 + return simple_read_from_buffer(buf, count, ppos, health, ret); 539 + } 540 + 541 + static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = { 542 + .read = pt5161l_debugfs_read_fw_load_sts, 543 + .open = simple_open, 544 + }; 545 + 546 + static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf, 547 + size_t count, loff_t *ppos) 548 + { 549 + struct pt5161l_data *data = file->private_data; 550 + int ret; 551 + bool status = false; 552 + char health[16]; 553 + 554 + mutex_lock(&data->lock); 555 + ret = pt5161l_heartbeat_check(data); 556 + mutex_unlock(&data->lock); 557 + if (ret == 0) 558 + status = data->mm_heartbeat_okay; 559 + 560 + ret = snprintf(health, sizeof(health), "%s\n", 561 + status ? "normal" : "abnormal"); 562 + 563 + return simple_read_from_buffer(buf, count, ppos, health, ret); 564 + } 565 + 566 + static const struct file_operations pt5161l_debugfs_ops_hb_sts = { 567 + .read = pt5161l_debugfs_read_hb_sts, 568 + .open = simple_open, 569 + }; 570 + 571 + static int pt5161l_init_debugfs(struct pt5161l_data *data) 572 + { 573 + data->debugfs = debugfs_create_dir(dev_name(&data->client->dev), 574 + pt5161l_debugfs_dir); 575 + 576 + debugfs_create_file("fw_ver", 0444, data->debugfs, data, 577 + &pt5161l_debugfs_ops_fw_ver); 578 + 579 + debugfs_create_file("fw_load_status", 0444, data->debugfs, data, 580 + &pt5161l_debugfs_ops_fw_load_sts); 581 + 582 + debugfs_create_file("heartbeat_status", 0444, data->debugfs, data, 583 + &pt5161l_debugfs_ops_hb_sts); 584 + 585 + return 0; 586 + } 587 + 588 + static int pt5161l_probe(struct i2c_client *client) 589 + { 590 + struct device *dev = &client->dev; 591 + struct device *hwmon_dev; 592 + struct pt5161l_data *data; 593 + 594 + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 595 + if (!data) 596 + return -ENOMEM; 597 + 598 + data->client = client; 599 + mutex_init(&data->lock); 600 + pt5161l_init_dev(data); 601 + dev_set_drvdata(dev, data); 602 + 603 + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, 604 + data, 605 + &pt5161l_chip_info, 606 + NULL); 607 + 608 + pt5161l_init_debugfs(data); 609 + 610 + return PTR_ERR_OR_ZERO(hwmon_dev); 611 + } 612 + 613 + static void pt5161l_remove(struct i2c_client *client) 614 + { 615 + struct pt5161l_data *data = i2c_get_clientdata(client); 616 + 617 + debugfs_remove_recursive(data->debugfs); 618 + } 619 + 620 + static const struct of_device_id __maybe_unused pt5161l_of_match[] = { 621 + { .compatible = "asteralabs,pt5161l" }, 622 + {}, 623 + }; 624 + MODULE_DEVICE_TABLE(of, pt5161l_of_match); 625 + 626 + static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = { 627 + { "PT5161L", 0 }, 628 + {}, 629 + }; 630 + MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match); 631 + 632 + static const struct i2c_device_id pt5161l_id[] = { 633 + { "pt5161l", 0 }, 634 + {} 635 + }; 636 + MODULE_DEVICE_TABLE(i2c, pt5161l_id); 637 + 638 + static struct i2c_driver pt5161l_driver = { 639 + .class = I2C_CLASS_HWMON, 640 + .driver = { 641 + .name = "pt5161l", 642 + .of_match_table = of_match_ptr(pt5161l_of_match), 643 + .acpi_match_table = ACPI_PTR(pt5161l_acpi_match), 644 + }, 645 + .probe = pt5161l_probe, 646 + .remove = pt5161l_remove, 647 + .id_table = pt5161l_id, 648 + }; 649 + 650 + static int __init pt5161l_init(void) 651 + { 652 + pt5161l_debugfs_dir = debugfs_create_dir("pt5161l", NULL); 653 + return i2c_add_driver(&pt5161l_driver); 654 + } 655 + 656 + static void __exit pt5161l_exit(void) 657 + { 658 + i2c_del_driver(&pt5161l_driver); 659 + debugfs_remove_recursive(pt5161l_debugfs_dir); 660 + } 661 + 662 + module_init(pt5161l_init); 663 + module_exit(pt5161l_exit); 664 + 665 + MODULE_AUTHOR("Cosmo Chou <cosmo.chou@quantatw.com>"); 666 + MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer"); 667 + MODULE_LICENSE("GPL");