xref: /openbmc/linux/drivers/hwmon/pt5161l.c (revision 3ae6b163)
15de33a7eSCosmo Chou // SPDX-License-Identifier: GPL-2.0-or-later
25de33a7eSCosmo Chou 
35de33a7eSCosmo Chou #include <linux/debugfs.h>
45de33a7eSCosmo Chou #include <linux/delay.h>
55de33a7eSCosmo Chou #include <linux/err.h>
65de33a7eSCosmo Chou #include <linux/i2c.h>
75de33a7eSCosmo Chou #include <linux/init.h>
85de33a7eSCosmo Chou #include <linux/hwmon.h>
95de33a7eSCosmo Chou #include <linux/module.h>
105de33a7eSCosmo Chou #include <linux/mutex.h>
115de33a7eSCosmo Chou 
125de33a7eSCosmo Chou /* Aries current average temp ADC code CSR */
135de33a7eSCosmo Chou #define ARIES_CURRENT_AVG_TEMP_ADC_CSR	0x42c
145de33a7eSCosmo Chou 
155de33a7eSCosmo Chou /* Device Load check register */
165de33a7eSCosmo Chou #define ARIES_CODE_LOAD_REG	0x605
175de33a7eSCosmo Chou /* Value indicating FW was loaded properly, [3:1] = 3'b111 */
185de33a7eSCosmo Chou #define ARIES_LOAD_CODE	0xe
195de33a7eSCosmo Chou 
205de33a7eSCosmo Chou /* Main Micro Heartbeat register */
215de33a7eSCosmo Chou #define ARIES_MM_HEARTBEAT_ADDR	0x923
225de33a7eSCosmo Chou 
235de33a7eSCosmo Chou /* Reg offset to specify Address for MM assisted accesses */
245de33a7eSCosmo Chou #define ARIES_MM_ASSIST_REG_ADDR_OFFSET	0xd99
255de33a7eSCosmo Chou /* Reg offset to specify Command for MM assisted accesses */
265de33a7eSCosmo Chou #define ARIES_MM_ASSIST_CMD_OFFSET	0xd9d
275de33a7eSCosmo Chou /* Reg offset to MM SPARE 0 used specify Address[7:0] */
285de33a7eSCosmo Chou #define ARIES_MM_ASSIST_SPARE_0_OFFSET	0xd9f
295de33a7eSCosmo Chou /* Reg offset to MM SPARE 3 used specify Data Byte 0 */
305de33a7eSCosmo Chou #define ARIES_MM_ASSIST_SPARE_3_OFFSET	0xda2
315de33a7eSCosmo Chou /* Wide register reads */
325de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_2B	0x1d
335de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_3B	0x1e
345de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_4B	0x1f
355de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_5B	0x20
365de33a7eSCosmo Chou 
375de33a7eSCosmo Chou /* Time delay between checking MM status of EEPROM write (microseconds) */
385de33a7eSCosmo Chou #define ARIES_MM_STATUS_TIME	5000
395de33a7eSCosmo Chou 
405de33a7eSCosmo Chou /* AL Main SRAM DMEM offset (A0) */
415de33a7eSCosmo Chou #define AL_MAIN_SRAM_DMEM_OFFSET	(64 * 1024)
425de33a7eSCosmo Chou /* SRAM read command */
435de33a7eSCosmo Chou #define AL_TG_RD_LOC_IND_SRAM	0x16
445de33a7eSCosmo Chou 
455de33a7eSCosmo Chou /* Offset for main micro FW info */
465de33a7eSCosmo Chou #define ARIES_MAIN_MICRO_FW_INFO	(96 * 1024 - 128)
475de33a7eSCosmo Chou /* FW Info (Major) offset location in struct */
485de33a7eSCosmo Chou #define ARIES_MM_FW_VERSION_MAJOR	0
495de33a7eSCosmo Chou /* FW Info (Minor) offset location in struct */
505de33a7eSCosmo Chou #define ARIES_MM_FW_VERSION_MINOR	1
515de33a7eSCosmo Chou /* FW Info (Build no.) offset location in struct */
525de33a7eSCosmo Chou #define ARIES_MM_FW_VERSION_BUILD	2
535de33a7eSCosmo Chou 
545de33a7eSCosmo Chou #define ARIES_TEMP_CAL_CODE_DEFAULT	84
555de33a7eSCosmo Chou 
565de33a7eSCosmo Chou /* Struct defining FW version loaded on an Aries device */
575de33a7eSCosmo Chou struct pt5161l_fw_ver {
585de33a7eSCosmo Chou 	u8 major;
595de33a7eSCosmo Chou 	u8 minor;
605de33a7eSCosmo Chou 	u16 build;
615de33a7eSCosmo Chou };
625de33a7eSCosmo Chou 
635de33a7eSCosmo Chou /* Each client has this additional data */
645de33a7eSCosmo Chou struct pt5161l_data {
655de33a7eSCosmo Chou 	struct i2c_client *client;
665de33a7eSCosmo Chou 	struct dentry *debugfs;
675de33a7eSCosmo Chou 	struct pt5161l_fw_ver fw_ver;
685de33a7eSCosmo Chou 	struct mutex lock; /* for atomic I2C transactions */
695de33a7eSCosmo Chou 	bool init_done;
705de33a7eSCosmo Chou 	bool code_load_okay; /* indicate if code load reg value is expected */
715de33a7eSCosmo Chou 	bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */
725de33a7eSCosmo Chou 	bool mm_wide_reg_access; /* MM assisted wide register access */
735de33a7eSCosmo Chou };
745de33a7eSCosmo Chou 
755de33a7eSCosmo Chou static struct dentry *pt5161l_debugfs_dir;
765de33a7eSCosmo Chou 
775de33a7eSCosmo Chou /*
785de33a7eSCosmo Chou  * Write multiple data bytes to Aries over I2C
795de33a7eSCosmo Chou  */
pt5161l_write_block_data(struct pt5161l_data * data,u32 address,u8 len,u8 * val)805de33a7eSCosmo Chou static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address,
815de33a7eSCosmo Chou 				    u8 len, u8 *val)
825de33a7eSCosmo Chou {
835de33a7eSCosmo Chou 	struct i2c_client *client = data->client;
845de33a7eSCosmo Chou 	int ret;
855de33a7eSCosmo Chou 	u8 remain_len = len;
865de33a7eSCosmo Chou 	u8 xfer_len, curr_len;
875de33a7eSCosmo Chou 	u8 buf[16];
885de33a7eSCosmo Chou 	u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
895de33a7eSCosmo Chou 	u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
905de33a7eSCosmo Chou 
915de33a7eSCosmo Chou 	while (remain_len > 0) {
925de33a7eSCosmo Chou 		if (remain_len > 4) {
935de33a7eSCosmo Chou 			curr_len = 4;
945de33a7eSCosmo Chou 			remain_len -= 4;
955de33a7eSCosmo Chou 		} else {
965de33a7eSCosmo Chou 			curr_len = remain_len;
975de33a7eSCosmo Chou 			remain_len = 0;
985de33a7eSCosmo Chou 		}
995de33a7eSCosmo Chou 
1005de33a7eSCosmo Chou 		buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1);
1015de33a7eSCosmo Chou 		buf[1] = (address >> 8) & 0xff;
1025de33a7eSCosmo Chou 		buf[2] = address & 0xff;
1035de33a7eSCosmo Chou 		memcpy(&buf[3], val, curr_len);
1045de33a7eSCosmo Chou 
1055de33a7eSCosmo Chou 		xfer_len = 3 + curr_len;
1065de33a7eSCosmo Chou 		ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf);
1075de33a7eSCosmo Chou 		if (ret)
1085de33a7eSCosmo Chou 			return ret;
1095de33a7eSCosmo Chou 
1105de33a7eSCosmo Chou 		val += curr_len;
1115de33a7eSCosmo Chou 		address += curr_len;
1125de33a7eSCosmo Chou 	}
1135de33a7eSCosmo Chou 
1145de33a7eSCosmo Chou 	return 0;
1155de33a7eSCosmo Chou }
1165de33a7eSCosmo Chou 
1175de33a7eSCosmo Chou /*
1185de33a7eSCosmo Chou  * Read multiple data bytes from Aries over I2C
1195de33a7eSCosmo Chou  */
pt5161l_read_block_data(struct pt5161l_data * data,u32 address,u8 len,u8 * val)1205de33a7eSCosmo Chou static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
1215de33a7eSCosmo Chou 				   u8 len, u8 *val)
1225de33a7eSCosmo Chou {
1235de33a7eSCosmo Chou 	struct i2c_client *client = data->client;
1245de33a7eSCosmo Chou 	int ret, tries;
1255de33a7eSCosmo Chou 	u8 remain_len = len;
1265de33a7eSCosmo Chou 	u8 curr_len;
1275de33a7eSCosmo Chou 	u8 wbuf[16], rbuf[24];
1285de33a7eSCosmo Chou 	u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */
1295de33a7eSCosmo Chou 	u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */
1305de33a7eSCosmo Chou 
1315de33a7eSCosmo Chou 	while (remain_len > 0) {
1325de33a7eSCosmo Chou 		if (remain_len > 16) {
1335de33a7eSCosmo Chou 			curr_len = 16;
1345de33a7eSCosmo Chou 			remain_len -= 16;
1355de33a7eSCosmo Chou 		} else {
1365de33a7eSCosmo Chou 			curr_len = remain_len;
1375de33a7eSCosmo Chou 			remain_len = 0;
1385de33a7eSCosmo Chou 		}
1395de33a7eSCosmo Chou 
1405de33a7eSCosmo Chou 		wbuf[0] = config | (curr_len - 1) << 1 |
1415de33a7eSCosmo Chou 			  ((address >> 16) & 0x1);
1425de33a7eSCosmo Chou 		wbuf[1] = (address >> 8) & 0xff;
1435de33a7eSCosmo Chou 		wbuf[2] = address & 0xff;
1445de33a7eSCosmo Chou 
1455de33a7eSCosmo Chou 		for (tries = 0; tries < 3; tries++) {
1465de33a7eSCosmo Chou 			ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3,
1475de33a7eSCosmo Chou 							 wbuf);
1485de33a7eSCosmo Chou 			if (ret)
1495de33a7eSCosmo Chou 				return ret;
1505de33a7eSCosmo Chou 
1515de33a7eSCosmo Chou 			ret = i2c_smbus_read_block_data(client, (cmd | 0x1),
1525de33a7eSCosmo Chou 							rbuf);
1535de33a7eSCosmo Chou 			if (ret == curr_len)
1545de33a7eSCosmo Chou 				break;
1555de33a7eSCosmo Chou 		}
1565de33a7eSCosmo Chou 		if (tries >= 3)
1575de33a7eSCosmo Chou 			return ret;
1585de33a7eSCosmo Chou 
1595de33a7eSCosmo Chou 		memcpy(val, rbuf, curr_len);
1605de33a7eSCosmo Chou 		val += curr_len;
1615de33a7eSCosmo Chou 		address += curr_len;
1625de33a7eSCosmo Chou 	}
1635de33a7eSCosmo Chou 
1645de33a7eSCosmo Chou 	return 0;
1655de33a7eSCosmo Chou }
1665de33a7eSCosmo Chou 
pt5161l_read_wide_reg(struct pt5161l_data * data,u32 address,u8 width,u8 * val)1675de33a7eSCosmo Chou static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address,
1685de33a7eSCosmo Chou 				 u8 width, u8 *val)
1695de33a7eSCosmo Chou {
1705de33a7eSCosmo Chou 	int ret, tries;
1715de33a7eSCosmo Chou 	u8 buf[8];
1725de33a7eSCosmo Chou 	u8 status;
1735de33a7eSCosmo Chou 
1745de33a7eSCosmo Chou 	/*
1755de33a7eSCosmo Chou 	 * Safely access wide registers using mailbox method to prevent
1765de33a7eSCosmo Chou 	 * risking conflict with Aries firmware; otherwise fallback to
1775de33a7eSCosmo Chou 	 * legacy, less secure method.
1785de33a7eSCosmo Chou 	 */
1795de33a7eSCosmo Chou 	if (data->mm_wide_reg_access) {
1805de33a7eSCosmo Chou 		buf[0] = address & 0xff;
1815de33a7eSCosmo Chou 		buf[1] = (address >> 8) & 0xff;
1825de33a7eSCosmo Chou 		buf[2] = (address >> 16) & 0x1;
1835de33a7eSCosmo Chou 		ret = pt5161l_write_block_data(data,
1845de33a7eSCosmo Chou 					       ARIES_MM_ASSIST_SPARE_0_OFFSET,
1855de33a7eSCosmo Chou 					       3, buf);
1865de33a7eSCosmo Chou 		if (ret)
1875de33a7eSCosmo Chou 			return ret;
1885de33a7eSCosmo Chou 
1895de33a7eSCosmo Chou 		/* Set command based on width */
1905de33a7eSCosmo Chou 		switch (width) {
1915de33a7eSCosmo Chou 		case 2:
1925de33a7eSCosmo Chou 			buf[0] = ARIES_MM_RD_WIDE_REG_2B;
1935de33a7eSCosmo Chou 			break;
1945de33a7eSCosmo Chou 		case 3:
1955de33a7eSCosmo Chou 			buf[0] = ARIES_MM_RD_WIDE_REG_3B;
1965de33a7eSCosmo Chou 			break;
1975de33a7eSCosmo Chou 		case 4:
1985de33a7eSCosmo Chou 			buf[0] = ARIES_MM_RD_WIDE_REG_4B;
1995de33a7eSCosmo Chou 			break;
2005de33a7eSCosmo Chou 		case 5:
2015de33a7eSCosmo Chou 			buf[0] = ARIES_MM_RD_WIDE_REG_5B;
2025de33a7eSCosmo Chou 			break;
2035de33a7eSCosmo Chou 		default:
2045de33a7eSCosmo Chou 			return -EINVAL;
2055de33a7eSCosmo Chou 		}
2065de33a7eSCosmo Chou 		ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET,
2075de33a7eSCosmo Chou 					       1, buf);
2085de33a7eSCosmo Chou 		if (ret)
2095de33a7eSCosmo Chou 			return ret;
2105de33a7eSCosmo Chou 
2115de33a7eSCosmo Chou 		status = 0xff;
2125de33a7eSCosmo Chou 		for (tries = 0; tries < 100; tries++) {
2135de33a7eSCosmo Chou 			ret = pt5161l_read_block_data(data,
2145de33a7eSCosmo Chou 						      ARIES_MM_ASSIST_CMD_OFFSET,
2155de33a7eSCosmo Chou 						      1, &status);
2165de33a7eSCosmo Chou 			if (ret)
2175de33a7eSCosmo Chou 				return ret;
2185de33a7eSCosmo Chou 
2195de33a7eSCosmo Chou 			if (status == 0)
2205de33a7eSCosmo Chou 				break;
2215de33a7eSCosmo Chou 
2225de33a7eSCosmo Chou 			usleep_range(ARIES_MM_STATUS_TIME,
2235de33a7eSCosmo Chou 				     ARIES_MM_STATUS_TIME + 1000);
2245de33a7eSCosmo Chou 		}
2255de33a7eSCosmo Chou 		if (status != 0)
2265de33a7eSCosmo Chou 			return -ETIMEDOUT;
2275de33a7eSCosmo Chou 
2285de33a7eSCosmo Chou 		ret = pt5161l_read_block_data(data,
2295de33a7eSCosmo Chou 					      ARIES_MM_ASSIST_SPARE_3_OFFSET,
2305de33a7eSCosmo Chou 					      width, val);
2315de33a7eSCosmo Chou 		if (ret)
2325de33a7eSCosmo Chou 			return ret;
2335de33a7eSCosmo Chou 	} else {
2345de33a7eSCosmo Chou 		return pt5161l_read_block_data(data, address, width, val);
2355de33a7eSCosmo Chou 	}
2365de33a7eSCosmo Chou 
2375de33a7eSCosmo Chou 	return 0;
2385de33a7eSCosmo Chou }
2395de33a7eSCosmo Chou 
2405de33a7eSCosmo Chou /*
2415de33a7eSCosmo Chou  * Read multiple (up to eight) data bytes from micro SRAM over I2C
2425de33a7eSCosmo Chou  */
2435de33a7eSCosmo Chou static int
pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data * data,u32 address,u8 len,u8 * val)2445de33a7eSCosmo Chou pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data,
2455de33a7eSCosmo Chou 					    u32 address, u8 len, u8 *val)
2465de33a7eSCosmo Chou {
2475de33a7eSCosmo Chou 	int ret, tries;
2485de33a7eSCosmo Chou 	u8 buf[8];
2495de33a7eSCosmo Chou 	u8 i, status;
2505de33a7eSCosmo Chou 	u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET;
2515de33a7eSCosmo Chou 	u32 eeprom_base, eeprom_addr;
2525de33a7eSCosmo Chou 
2535de33a7eSCosmo Chou 	/* No multi-byte indirect support here. Hence read a byte at a time */
2545de33a7eSCosmo Chou 	eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET;
2555de33a7eSCosmo Chou 	for (i = 0; i < len; i++) {
2565de33a7eSCosmo Chou 		eeprom_addr = eeprom_base + i;
2575de33a7eSCosmo Chou 		buf[0] = eeprom_addr & 0xff;
2585de33a7eSCosmo Chou 		buf[1] = (eeprom_addr >> 8) & 0xff;
2595de33a7eSCosmo Chou 		buf[2] = (eeprom_addr >> 16) & 0xff;
2605de33a7eSCosmo Chou 		ret = pt5161l_write_block_data(data, uind_offs, 3, buf);
2615de33a7eSCosmo Chou 		if (ret)
2625de33a7eSCosmo Chou 			return ret;
2635de33a7eSCosmo Chou 
2645de33a7eSCosmo Chou 		buf[0] = AL_TG_RD_LOC_IND_SRAM;
2655de33a7eSCosmo Chou 		ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf);
2665de33a7eSCosmo Chou 		if (ret)
2675de33a7eSCosmo Chou 			return ret;
2685de33a7eSCosmo Chou 
2695de33a7eSCosmo Chou 		status = 0xff;
2705de33a7eSCosmo Chou 		for (tries = 0; tries < 255; tries++) {
2715de33a7eSCosmo Chou 			ret = pt5161l_read_block_data(data, uind_offs + 4, 1,
2725de33a7eSCosmo Chou 						      &status);
2735de33a7eSCosmo Chou 			if (ret)
2745de33a7eSCosmo Chou 				return ret;
2755de33a7eSCosmo Chou 
2765de33a7eSCosmo Chou 			if (status == 0)
2775de33a7eSCosmo Chou 				break;
2785de33a7eSCosmo Chou 		}
2795de33a7eSCosmo Chou 		if (status != 0)
2805de33a7eSCosmo Chou 			return -ETIMEDOUT;
2815de33a7eSCosmo Chou 
2825de33a7eSCosmo Chou 		ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf);
2835de33a7eSCosmo Chou 		if (ret)
2845de33a7eSCosmo Chou 			return ret;
2855de33a7eSCosmo Chou 
2865de33a7eSCosmo Chou 		val[i] = buf[0];
2875de33a7eSCosmo Chou 	}
2885de33a7eSCosmo Chou 
2895de33a7eSCosmo Chou 	return 0;
2905de33a7eSCosmo Chou }
2915de33a7eSCosmo Chou 
2925de33a7eSCosmo Chou /*
2935de33a7eSCosmo Chou  * Check firmware load status
2945de33a7eSCosmo Chou  */
pt5161l_fw_load_check(struct pt5161l_data * data)2955de33a7eSCosmo Chou static int pt5161l_fw_load_check(struct pt5161l_data *data)
2965de33a7eSCosmo Chou {
2975de33a7eSCosmo Chou 	int ret;
2985de33a7eSCosmo Chou 	u8 buf[8];
2995de33a7eSCosmo Chou 
3005de33a7eSCosmo Chou 	ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf);
3015de33a7eSCosmo Chou 	if (ret)
3025de33a7eSCosmo Chou 		return ret;
3035de33a7eSCosmo Chou 
3045de33a7eSCosmo Chou 	if (buf[0] < ARIES_LOAD_CODE) {
3055de33a7eSCosmo Chou 		dev_dbg(&data->client->dev,
3065de33a7eSCosmo Chou 			"Code Load reg unexpected. Not all modules are loaded %x\n",
3075de33a7eSCosmo Chou 			buf[0]);
3085de33a7eSCosmo Chou 		data->code_load_okay = false;
3095de33a7eSCosmo Chou 	} else {
3105de33a7eSCosmo Chou 		data->code_load_okay = true;
3115de33a7eSCosmo Chou 	}
3125de33a7eSCosmo Chou 
3135de33a7eSCosmo Chou 	return 0;
3145de33a7eSCosmo Chou }
3155de33a7eSCosmo Chou 
3165de33a7eSCosmo Chou /*
3175de33a7eSCosmo Chou  * Check main micro heartbeat
3185de33a7eSCosmo Chou  */
pt5161l_heartbeat_check(struct pt5161l_data * data)3195de33a7eSCosmo Chou static int pt5161l_heartbeat_check(struct pt5161l_data *data)
3205de33a7eSCosmo Chou {
3215de33a7eSCosmo Chou 	int ret, tries;
3225de33a7eSCosmo Chou 	u8 buf[8];
3235de33a7eSCosmo Chou 	u8 heartbeat;
3245de33a7eSCosmo Chou 	bool hb_changed = false;
3255de33a7eSCosmo Chou 
3265de33a7eSCosmo Chou 	ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf);
3275de33a7eSCosmo Chou 	if (ret)
3285de33a7eSCosmo Chou 		return ret;
3295de33a7eSCosmo Chou 
3305de33a7eSCosmo Chou 	heartbeat = buf[0];
3315de33a7eSCosmo Chou 	for (tries = 0; tries < 100; tries++) {
3325de33a7eSCosmo Chou 		ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1,
3335de33a7eSCosmo Chou 					      buf);
3345de33a7eSCosmo Chou 		if (ret)
3355de33a7eSCosmo Chou 			return ret;
3365de33a7eSCosmo Chou 
3375de33a7eSCosmo Chou 		if (buf[0] != heartbeat) {
3385de33a7eSCosmo Chou 			hb_changed = true;
3395de33a7eSCosmo Chou 			break;
3405de33a7eSCosmo Chou 		}
3415de33a7eSCosmo Chou 	}
3425de33a7eSCosmo Chou 	data->mm_heartbeat_okay = hb_changed;
3435de33a7eSCosmo Chou 
3445de33a7eSCosmo Chou 	return 0;
3455de33a7eSCosmo Chou }
3465de33a7eSCosmo Chou 
3475de33a7eSCosmo Chou /*
3485de33a7eSCosmo Chou  * Check the status of firmware
3495de33a7eSCosmo Chou  */
pt5161l_fwsts_check(struct pt5161l_data * data)3505de33a7eSCosmo Chou static int pt5161l_fwsts_check(struct pt5161l_data *data)
3515de33a7eSCosmo Chou {
3525de33a7eSCosmo Chou 	int ret;
3535de33a7eSCosmo Chou 	u8 buf[8];
3545de33a7eSCosmo Chou 	u8 major = 0, minor = 0;
3555de33a7eSCosmo Chou 	u16 build = 0;
3565de33a7eSCosmo Chou 
3575de33a7eSCosmo Chou 	ret = pt5161l_fw_load_check(data);
3585de33a7eSCosmo Chou 	if (ret)
3595de33a7eSCosmo Chou 		return ret;
3605de33a7eSCosmo Chou 
3615de33a7eSCosmo Chou 	ret = pt5161l_heartbeat_check(data);
3625de33a7eSCosmo Chou 	if (ret)
3635de33a7eSCosmo Chou 		return ret;
3645de33a7eSCosmo Chou 
3655de33a7eSCosmo Chou 	if (data->code_load_okay && data->mm_heartbeat_okay) {
3665de33a7eSCosmo Chou 		ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
3675de33a7eSCosmo Chou 								  ARIES_MM_FW_VERSION_MAJOR,
3685de33a7eSCosmo Chou 								  1, &major);
3695de33a7eSCosmo Chou 		if (ret)
3705de33a7eSCosmo Chou 			return ret;
3715de33a7eSCosmo Chou 
3725de33a7eSCosmo Chou 		ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
3735de33a7eSCosmo Chou 								  ARIES_MM_FW_VERSION_MINOR,
3745de33a7eSCosmo Chou 								  1, &minor);
3755de33a7eSCosmo Chou 		if (ret)
3765de33a7eSCosmo Chou 			return ret;
3775de33a7eSCosmo Chou 
3785de33a7eSCosmo Chou 		ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO +
3795de33a7eSCosmo Chou 								  ARIES_MM_FW_VERSION_BUILD,
3805de33a7eSCosmo Chou 								  2, buf);
3815de33a7eSCosmo Chou 		if (ret)
3825de33a7eSCosmo Chou 			return ret;
3835de33a7eSCosmo Chou 		build = buf[1] << 8 | buf[0];
3845de33a7eSCosmo Chou 	}
3855de33a7eSCosmo Chou 	data->fw_ver.major = major;
3865de33a7eSCosmo Chou 	data->fw_ver.minor = minor;
3875de33a7eSCosmo Chou 	data->fw_ver.build = build;
3885de33a7eSCosmo Chou 
3895de33a7eSCosmo Chou 	return 0;
3905de33a7eSCosmo Chou }
3915de33a7eSCosmo Chou 
pt5161l_fw_is_at_least(struct pt5161l_data * data,u8 major,u8 minor,u16 build)3925de33a7eSCosmo Chou static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor,
3935de33a7eSCosmo Chou 				  u16 build)
3945de33a7eSCosmo Chou {
3955de33a7eSCosmo Chou 	u32 ver = major << 24 | minor << 16 | build;
3965de33a7eSCosmo Chou 	u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 |
3975de33a7eSCosmo Chou 		       data->fw_ver.build;
3985de33a7eSCosmo Chou 
3995de33a7eSCosmo Chou 	if (curr_ver >= ver)
4005de33a7eSCosmo Chou 		return true;
4015de33a7eSCosmo Chou 
4025de33a7eSCosmo Chou 	return false;
4035de33a7eSCosmo Chou }
4045de33a7eSCosmo Chou 
pt5161l_init_dev(struct pt5161l_data * data)4055de33a7eSCosmo Chou static int pt5161l_init_dev(struct pt5161l_data *data)
4065de33a7eSCosmo Chou {
4075de33a7eSCosmo Chou 	int ret;
4085de33a7eSCosmo Chou 
4095de33a7eSCosmo Chou 	mutex_lock(&data->lock);
4105de33a7eSCosmo Chou 	ret = pt5161l_fwsts_check(data);
4115de33a7eSCosmo Chou 	mutex_unlock(&data->lock);
4125de33a7eSCosmo Chou 	if (ret)
4135de33a7eSCosmo Chou 		return ret;
4145de33a7eSCosmo Chou 
4155de33a7eSCosmo Chou 	/* Firmware 2.2.0 enables safe access to wide registers */
4165de33a7eSCosmo Chou 	if (pt5161l_fw_is_at_least(data, 2, 2, 0))
4175de33a7eSCosmo Chou 		data->mm_wide_reg_access = true;
4185de33a7eSCosmo Chou 
4195de33a7eSCosmo Chou 	data->init_done = true;
4205de33a7eSCosmo Chou 
4215de33a7eSCosmo Chou 	return 0;
4225de33a7eSCosmo Chou }
4235de33a7eSCosmo Chou 
pt5161l_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)4245de33a7eSCosmo Chou static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type,
4255de33a7eSCosmo Chou 			u32 attr, int channel, long *val)
4265de33a7eSCosmo Chou {
4275de33a7eSCosmo Chou 	struct pt5161l_data *data = dev_get_drvdata(dev);
4285de33a7eSCosmo Chou 	int ret;
4295de33a7eSCosmo Chou 	u8 buf[8];
430*3ae6b163SCosmo Chou 	u32 adc_code;
4315de33a7eSCosmo Chou 
4325de33a7eSCosmo Chou 	switch (attr) {
4335de33a7eSCosmo Chou 	case hwmon_temp_input:
4345de33a7eSCosmo Chou 		if (!data->init_done) {
4355de33a7eSCosmo Chou 			ret = pt5161l_init_dev(data);
4365de33a7eSCosmo Chou 			if (ret)
4375de33a7eSCosmo Chou 				return ret;
4385de33a7eSCosmo Chou 		}
4395de33a7eSCosmo Chou 
4405de33a7eSCosmo Chou 		mutex_lock(&data->lock);
4415de33a7eSCosmo Chou 		ret = pt5161l_read_wide_reg(data,
4425de33a7eSCosmo Chou 					    ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4,
4435de33a7eSCosmo Chou 					    buf);
4445de33a7eSCosmo Chou 		mutex_unlock(&data->lock);
4455de33a7eSCosmo Chou 		if (ret) {
4465de33a7eSCosmo Chou 			dev_dbg(dev, "Read adc_code failed %d\n", ret);
4475de33a7eSCosmo Chou 			return ret;
4485de33a7eSCosmo Chou 		}
4495de33a7eSCosmo Chou 
4505de33a7eSCosmo Chou 		adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
4515de33a7eSCosmo Chou 		if (adc_code == 0 || adc_code >= 0x3ff) {
452*3ae6b163SCosmo Chou 			dev_dbg(dev, "Invalid adc_code %x\n", adc_code);
4535de33a7eSCosmo Chou 			return -EIO;
4545de33a7eSCosmo Chou 		}
4555de33a7eSCosmo Chou 
4565de33a7eSCosmo Chou 		*val = 110000 +
4575de33a7eSCosmo Chou 		       ((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) *
4585de33a7eSCosmo Chou 			-320);
4595de33a7eSCosmo Chou 		break;
4605de33a7eSCosmo Chou 	default:
4615de33a7eSCosmo Chou 		return -EOPNOTSUPP;
4625de33a7eSCosmo Chou 	}
4635de33a7eSCosmo Chou 
4645de33a7eSCosmo Chou 	return 0;
4655de33a7eSCosmo Chou }
4665de33a7eSCosmo Chou 
pt5161l_is_visible(const void * data,enum hwmon_sensor_types type,u32 attr,int channel)4675de33a7eSCosmo Chou static umode_t pt5161l_is_visible(const void *data,
4685de33a7eSCosmo Chou 				  enum hwmon_sensor_types type, u32 attr,
4695de33a7eSCosmo Chou 				  int channel)
4705de33a7eSCosmo Chou {
4715de33a7eSCosmo Chou 	switch (attr) {
4725de33a7eSCosmo Chou 	case hwmon_temp_input:
4735de33a7eSCosmo Chou 		return 0444;
4745de33a7eSCosmo Chou 	default:
4755de33a7eSCosmo Chou 		break;
4765de33a7eSCosmo Chou 	}
4775de33a7eSCosmo Chou 
4785de33a7eSCosmo Chou 	return 0;
4795de33a7eSCosmo Chou }
4805de33a7eSCosmo Chou 
4815de33a7eSCosmo Chou static const struct hwmon_channel_info *pt5161l_info[] = {
4825de33a7eSCosmo Chou 	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
4835de33a7eSCosmo Chou 	NULL
4845de33a7eSCosmo Chou };
4855de33a7eSCosmo Chou 
4865de33a7eSCosmo Chou static const struct hwmon_ops pt5161l_hwmon_ops = {
4875de33a7eSCosmo Chou 	.is_visible = pt5161l_is_visible,
4885de33a7eSCosmo Chou 	.read = pt5161l_read,
4895de33a7eSCosmo Chou };
4905de33a7eSCosmo Chou 
4915de33a7eSCosmo Chou static const struct hwmon_chip_info pt5161l_chip_info = {
4925de33a7eSCosmo Chou 	.ops = &pt5161l_hwmon_ops,
4935de33a7eSCosmo Chou 	.info = pt5161l_info,
4945de33a7eSCosmo Chou };
4955de33a7eSCosmo Chou 
pt5161l_debugfs_read_fw_ver(struct file * file,char __user * buf,size_t count,loff_t * ppos)4965de33a7eSCosmo Chou static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf,
4975de33a7eSCosmo Chou 					   size_t count, loff_t *ppos)
4985de33a7eSCosmo Chou {
4995de33a7eSCosmo Chou 	struct pt5161l_data *data = file->private_data;
5005de33a7eSCosmo Chou 	int ret;
5015de33a7eSCosmo Chou 	char ver[32];
5025de33a7eSCosmo Chou 
5035de33a7eSCosmo Chou 	mutex_lock(&data->lock);
5045de33a7eSCosmo Chou 	ret = pt5161l_fwsts_check(data);
5055de33a7eSCosmo Chou 	mutex_unlock(&data->lock);
5065de33a7eSCosmo Chou 	if (ret)
5075de33a7eSCosmo Chou 		return ret;
5085de33a7eSCosmo Chou 
5095de33a7eSCosmo Chou 	ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major,
5105de33a7eSCosmo Chou 		       data->fw_ver.minor, data->fw_ver.build);
5115de33a7eSCosmo Chou 
5125de33a7eSCosmo Chou 	return simple_read_from_buffer(buf, count, ppos, ver, ret);
5135de33a7eSCosmo Chou }
5145de33a7eSCosmo Chou 
5155de33a7eSCosmo Chou static const struct file_operations pt5161l_debugfs_ops_fw_ver = {
5165de33a7eSCosmo Chou 	.read = pt5161l_debugfs_read_fw_ver,
5175de33a7eSCosmo Chou 	.open = simple_open,
5185de33a7eSCosmo Chou };
5195de33a7eSCosmo Chou 
pt5161l_debugfs_read_fw_load_sts(struct file * file,char __user * buf,size_t count,loff_t * ppos)5205de33a7eSCosmo Chou static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file,
5215de33a7eSCosmo Chou 						char __user *buf, size_t count,
5225de33a7eSCosmo Chou 						loff_t *ppos)
5235de33a7eSCosmo Chou {
5245de33a7eSCosmo Chou 	struct pt5161l_data *data = file->private_data;
5255de33a7eSCosmo Chou 	int ret;
5265de33a7eSCosmo Chou 	bool status = false;
5275de33a7eSCosmo Chou 	char health[16];
5285de33a7eSCosmo Chou 
5295de33a7eSCosmo Chou 	mutex_lock(&data->lock);
5305de33a7eSCosmo Chou 	ret = pt5161l_fw_load_check(data);
5315de33a7eSCosmo Chou 	mutex_unlock(&data->lock);
5325de33a7eSCosmo Chou 	if (ret == 0)
5335de33a7eSCosmo Chou 		status = data->code_load_okay;
5345de33a7eSCosmo Chou 
5355de33a7eSCosmo Chou 	ret = snprintf(health, sizeof(health), "%s\n",
5365de33a7eSCosmo Chou 		       status ? "normal" : "abnormal");
5375de33a7eSCosmo Chou 
5385de33a7eSCosmo Chou 	return simple_read_from_buffer(buf, count, ppos, health, ret);
5395de33a7eSCosmo Chou }
5405de33a7eSCosmo Chou 
5415de33a7eSCosmo Chou static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = {
5425de33a7eSCosmo Chou 	.read = pt5161l_debugfs_read_fw_load_sts,
5435de33a7eSCosmo Chou 	.open = simple_open,
5445de33a7eSCosmo Chou };
5455de33a7eSCosmo Chou 
pt5161l_debugfs_read_hb_sts(struct file * file,char __user * buf,size_t count,loff_t * ppos)5465de33a7eSCosmo Chou static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf,
5475de33a7eSCosmo Chou 					   size_t count, loff_t *ppos)
5485de33a7eSCosmo Chou {
5495de33a7eSCosmo Chou 	struct pt5161l_data *data = file->private_data;
5505de33a7eSCosmo Chou 	int ret;
5515de33a7eSCosmo Chou 	bool status = false;
5525de33a7eSCosmo Chou 	char health[16];
5535de33a7eSCosmo Chou 
5545de33a7eSCosmo Chou 	mutex_lock(&data->lock);
5555de33a7eSCosmo Chou 	ret = pt5161l_heartbeat_check(data);
5565de33a7eSCosmo Chou 	mutex_unlock(&data->lock);
5575de33a7eSCosmo Chou 	if (ret == 0)
5585de33a7eSCosmo Chou 		status = data->mm_heartbeat_okay;
5595de33a7eSCosmo Chou 
5605de33a7eSCosmo Chou 	ret = snprintf(health, sizeof(health), "%s\n",
5615de33a7eSCosmo Chou 		       status ? "normal" : "abnormal");
5625de33a7eSCosmo Chou 
5635de33a7eSCosmo Chou 	return simple_read_from_buffer(buf, count, ppos, health, ret);
5645de33a7eSCosmo Chou }
5655de33a7eSCosmo Chou 
5665de33a7eSCosmo Chou static const struct file_operations pt5161l_debugfs_ops_hb_sts = {
5675de33a7eSCosmo Chou 	.read = pt5161l_debugfs_read_hb_sts,
5685de33a7eSCosmo Chou 	.open = simple_open,
5695de33a7eSCosmo Chou };
5705de33a7eSCosmo Chou 
pt5161l_init_debugfs(struct pt5161l_data * data)5715de33a7eSCosmo Chou static int pt5161l_init_debugfs(struct pt5161l_data *data)
5725de33a7eSCosmo Chou {
5735de33a7eSCosmo Chou 	data->debugfs = debugfs_create_dir(dev_name(&data->client->dev),
5745de33a7eSCosmo Chou 					   pt5161l_debugfs_dir);
5755de33a7eSCosmo Chou 
5765de33a7eSCosmo Chou 	debugfs_create_file("fw_ver", 0444, data->debugfs, data,
5775de33a7eSCosmo Chou 			    &pt5161l_debugfs_ops_fw_ver);
5785de33a7eSCosmo Chou 
5795de33a7eSCosmo Chou 	debugfs_create_file("fw_load_status", 0444, data->debugfs, data,
5805de33a7eSCosmo Chou 			    &pt5161l_debugfs_ops_fw_load_sts);
5815de33a7eSCosmo Chou 
5825de33a7eSCosmo Chou 	debugfs_create_file("heartbeat_status", 0444, data->debugfs, data,
5835de33a7eSCosmo Chou 			    &pt5161l_debugfs_ops_hb_sts);
5845de33a7eSCosmo Chou 
5855de33a7eSCosmo Chou 	return 0;
5865de33a7eSCosmo Chou }
5875de33a7eSCosmo Chou 
pt5161l_probe(struct i2c_client * client)5885de33a7eSCosmo Chou static int pt5161l_probe(struct i2c_client *client)
5895de33a7eSCosmo Chou {
5905de33a7eSCosmo Chou 	struct device *dev = &client->dev;
5915de33a7eSCosmo Chou 	struct device *hwmon_dev;
5925de33a7eSCosmo Chou 	struct pt5161l_data *data;
5935de33a7eSCosmo Chou 
5945de33a7eSCosmo Chou 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
5955de33a7eSCosmo Chou 	if (!data)
5965de33a7eSCosmo Chou 		return -ENOMEM;
5975de33a7eSCosmo Chou 
5985de33a7eSCosmo Chou 	data->client = client;
5995de33a7eSCosmo Chou 	mutex_init(&data->lock);
6005de33a7eSCosmo Chou 	pt5161l_init_dev(data);
6015de33a7eSCosmo Chou 	dev_set_drvdata(dev, data);
6025de33a7eSCosmo Chou 
6035de33a7eSCosmo Chou 	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
6045de33a7eSCosmo Chou 							 data,
6055de33a7eSCosmo Chou 							 &pt5161l_chip_info,
6065de33a7eSCosmo Chou 							 NULL);
6075de33a7eSCosmo Chou 
6085de33a7eSCosmo Chou 	pt5161l_init_debugfs(data);
6095de33a7eSCosmo Chou 
6105de33a7eSCosmo Chou 	return PTR_ERR_OR_ZERO(hwmon_dev);
6115de33a7eSCosmo Chou }
6125de33a7eSCosmo Chou 
pt5161l_remove(struct i2c_client * client)6135de33a7eSCosmo Chou static void pt5161l_remove(struct i2c_client *client)
6145de33a7eSCosmo Chou {
6155de33a7eSCosmo Chou 	struct pt5161l_data *data = i2c_get_clientdata(client);
6165de33a7eSCosmo Chou 
6175de33a7eSCosmo Chou 	debugfs_remove_recursive(data->debugfs);
6185de33a7eSCosmo Chou }
6195de33a7eSCosmo Chou 
6205de33a7eSCosmo Chou static const struct of_device_id __maybe_unused pt5161l_of_match[] = {
6215de33a7eSCosmo Chou 	{ .compatible = "asteralabs,pt5161l" },
6225de33a7eSCosmo Chou 	{},
6235de33a7eSCosmo Chou };
6245de33a7eSCosmo Chou MODULE_DEVICE_TABLE(of, pt5161l_of_match);
6255de33a7eSCosmo Chou 
6265de33a7eSCosmo Chou static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = {
6275de33a7eSCosmo Chou 	{ "PT5161L", 0 },
6285de33a7eSCosmo Chou 	{},
6295de33a7eSCosmo Chou };
6305de33a7eSCosmo Chou MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match);
6315de33a7eSCosmo Chou 
6325de33a7eSCosmo Chou static const struct i2c_device_id pt5161l_id[] = {
6335de33a7eSCosmo Chou 	{ "pt5161l", 0 },
6345de33a7eSCosmo Chou 	{}
6355de33a7eSCosmo Chou };
6365de33a7eSCosmo Chou MODULE_DEVICE_TABLE(i2c, pt5161l_id);
6375de33a7eSCosmo Chou 
6385de33a7eSCosmo Chou static struct i2c_driver pt5161l_driver = {
6395de33a7eSCosmo Chou 	.class = I2C_CLASS_HWMON,
6405de33a7eSCosmo Chou 	.driver = {
6415de33a7eSCosmo Chou 		.name = "pt5161l",
6425de33a7eSCosmo Chou 		.of_match_table = of_match_ptr(pt5161l_of_match),
6435de33a7eSCosmo Chou 		.acpi_match_table = ACPI_PTR(pt5161l_acpi_match),
6445de33a7eSCosmo Chou 	},
6455de33a7eSCosmo Chou 	.probe = pt5161l_probe,
6465de33a7eSCosmo Chou 	.remove = pt5161l_remove,
6475de33a7eSCosmo Chou 	.id_table = pt5161l_id,
6485de33a7eSCosmo Chou };
6495de33a7eSCosmo Chou 
pt5161l_init(void)6505de33a7eSCosmo Chou static int __init pt5161l_init(void)
6515de33a7eSCosmo Chou {
6525de33a7eSCosmo Chou 	pt5161l_debugfs_dir = debugfs_create_dir("pt5161l", NULL);
6535de33a7eSCosmo Chou 	return i2c_add_driver(&pt5161l_driver);
6545de33a7eSCosmo Chou }
6555de33a7eSCosmo Chou 
pt5161l_exit(void)6565de33a7eSCosmo Chou static void __exit pt5161l_exit(void)
6575de33a7eSCosmo Chou {
6585de33a7eSCosmo Chou 	i2c_del_driver(&pt5161l_driver);
6595de33a7eSCosmo Chou 	debugfs_remove_recursive(pt5161l_debugfs_dir);
6605de33a7eSCosmo Chou }
6615de33a7eSCosmo Chou 
6625de33a7eSCosmo Chou module_init(pt5161l_init);
6635de33a7eSCosmo Chou module_exit(pt5161l_exit);
6645de33a7eSCosmo Chou 
6655de33a7eSCosmo Chou MODULE_AUTHOR("Cosmo Chou <cosmo.chou@quantatw.com>");
6665de33a7eSCosmo Chou MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer");
6675de33a7eSCosmo Chou MODULE_LICENSE("GPL");
668