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