xref: /openbmc/linux/drivers/hwmon/occ/common.c (revision cf8e9f21)
1e2f05d60SEddie James // SPDX-License-Identifier: GPL-2.0+
2e2f05d60SEddie James // Copyright IBM Corp 2019
35b5513b8SEddie James 
45b5513b8SEddie James #include <linux/device.h>
55679ed99SJean Delvare #include <linux/export.h>
654076cb3SEddie James #include <linux/hwmon.h>
7c10e753dSEddie James #include <linux/hwmon-sysfs.h>
8c10e753dSEddie James #include <linux/jiffies.h>
9aa195fe4SEddie James #include <linux/kernel.h>
10c10e753dSEddie James #include <linux/math64.h>
115679ed99SJean Delvare #include <linux/module.h>
12c10e753dSEddie James #include <linux/mutex.h>
1354076cb3SEddie James #include <linux/sysfs.h>
14c10e753dSEddie James #include <asm/unaligned.h>
155b5513b8SEddie James 
165b5513b8SEddie James #include "common.h"
175b5513b8SEddie James 
18c10e753dSEddie James #define EXTN_FLAG_SENSOR_ID		BIT(7)
19c10e753dSEddie James 
20df04ced6SEddie James #define OCC_ERROR_COUNT_THRESHOLD	2	/* required by OCC spec */
21df04ced6SEddie James 
22df04ced6SEddie James #define OCC_STATE_SAFE			4
23df04ced6SEddie James #define OCC_SAFE_TIMEOUT		msecs_to_jiffies(60000) /* 1 min */
24df04ced6SEddie James 
25c10e753dSEddie James #define OCC_UPDATE_FREQUENCY		msecs_to_jiffies(1000)
26c10e753dSEddie James 
27c10e753dSEddie James #define OCC_TEMP_SENSOR_FAULT		0xFF
28c10e753dSEddie James 
29c10e753dSEddie James #define OCC_FRU_TYPE_VRM		3
30c10e753dSEddie James 
31c10e753dSEddie James /* OCC sensor type and version definitions */
32c10e753dSEddie James 
33c10e753dSEddie James struct temp_sensor_1 {
34c10e753dSEddie James 	u16 sensor_id;
35c10e753dSEddie James 	u16 value;
36c10e753dSEddie James } __packed;
37c10e753dSEddie James 
38c10e753dSEddie James struct temp_sensor_2 {
39c10e753dSEddie James 	u32 sensor_id;
40c10e753dSEddie James 	u8 fru_type;
41c10e753dSEddie James 	u8 value;
42c10e753dSEddie James } __packed;
43c10e753dSEddie James 
44db4919ecSEddie James struct temp_sensor_10 {
45db4919ecSEddie James 	u32 sensor_id;
46db4919ecSEddie James 	u8 fru_type;
47db4919ecSEddie James 	u8 value;
48db4919ecSEddie James 	u8 throttle;
49db4919ecSEddie James 	u8 reserved;
50db4919ecSEddie James } __packed;
51db4919ecSEddie James 
52c10e753dSEddie James struct freq_sensor_1 {
53c10e753dSEddie James 	u16 sensor_id;
54c10e753dSEddie James 	u16 value;
55c10e753dSEddie James } __packed;
56c10e753dSEddie James 
57c10e753dSEddie James struct freq_sensor_2 {
58c10e753dSEddie James 	u32 sensor_id;
59c10e753dSEddie James 	u16 value;
60c10e753dSEddie James } __packed;
61c10e753dSEddie James 
62c10e753dSEddie James struct power_sensor_1 {
63c10e753dSEddie James 	u16 sensor_id;
64c10e753dSEddie James 	u32 update_tag;
65c10e753dSEddie James 	u32 accumulator;
66c10e753dSEddie James 	u16 value;
67c10e753dSEddie James } __packed;
68c10e753dSEddie James 
69c10e753dSEddie James struct power_sensor_2 {
70c10e753dSEddie James 	u32 sensor_id;
71c10e753dSEddie James 	u8 function_id;
72c10e753dSEddie James 	u8 apss_channel;
73c10e753dSEddie James 	u16 reserved;
74c10e753dSEddie James 	u32 update_tag;
75c10e753dSEddie James 	u64 accumulator;
76c10e753dSEddie James 	u16 value;
77c10e753dSEddie James } __packed;
78c10e753dSEddie James 
79c10e753dSEddie James struct power_sensor_data {
80c10e753dSEddie James 	u16 value;
81c10e753dSEddie James 	u32 update_tag;
82c10e753dSEddie James 	u64 accumulator;
83c10e753dSEddie James } __packed;
84c10e753dSEddie James 
85c10e753dSEddie James struct power_sensor_data_and_time {
86c10e753dSEddie James 	u16 update_time;
87c10e753dSEddie James 	u16 value;
88c10e753dSEddie James 	u32 update_tag;
89c10e753dSEddie James 	u64 accumulator;
90c10e753dSEddie James } __packed;
91c10e753dSEddie James 
92c10e753dSEddie James struct power_sensor_a0 {
93c10e753dSEddie James 	u32 sensor_id;
94c10e753dSEddie James 	struct power_sensor_data_and_time system;
95c10e753dSEddie James 	u32 reserved;
96c10e753dSEddie James 	struct power_sensor_data_and_time proc;
97c10e753dSEddie James 	struct power_sensor_data vdd;
98c10e753dSEddie James 	struct power_sensor_data vdn;
99c10e753dSEddie James } __packed;
100c10e753dSEddie James 
101c10e753dSEddie James struct caps_sensor_2 {
102c10e753dSEddie James 	u16 cap;
103c10e753dSEddie James 	u16 system_power;
104c10e753dSEddie James 	u16 n_cap;
105c10e753dSEddie James 	u16 max;
106c10e753dSEddie James 	u16 min;
107c10e753dSEddie James 	u16 user;
108c10e753dSEddie James 	u8 user_source;
109c10e753dSEddie James } __packed;
110c10e753dSEddie James 
111c10e753dSEddie James struct caps_sensor_3 {
112c10e753dSEddie James 	u16 cap;
113c10e753dSEddie James 	u16 system_power;
114c10e753dSEddie James 	u16 n_cap;
115c10e753dSEddie James 	u16 max;
116c10e753dSEddie James 	u16 hard_min;
117c10e753dSEddie James 	u16 soft_min;
118c10e753dSEddie James 	u16 user;
119c10e753dSEddie James 	u8 user_source;
120c10e753dSEddie James } __packed;
121c10e753dSEddie James 
122c10e753dSEddie James struct extended_sensor {
123c10e753dSEddie James 	union {
124c10e753dSEddie James 		u8 name[4];
125c10e753dSEddie James 		u32 sensor_id;
126c10e753dSEddie James 	};
127c10e753dSEddie James 	u8 flags;
128c10e753dSEddie James 	u8 reserved;
129c10e753dSEddie James 	u8 data[6];
130c10e753dSEddie James } __packed;
131c10e753dSEddie James 
1325b5513b8SEddie James static int occ_poll(struct occ *occ)
1335b5513b8SEddie James {
134df04ced6SEddie James 	int rc;
135908dbf02SEddie James 	u8 cmd[7];
136df04ced6SEddie James 	struct occ_poll_response_header *header;
1375b5513b8SEddie James 
1385b5513b8SEddie James 	/* big endian */
139908dbf02SEddie James 	cmd[0] = 0;			/* sequence number */
1405b5513b8SEddie James 	cmd[1] = 0;			/* cmd type */
1415b5513b8SEddie James 	cmd[2] = 0;			/* data length msb */
1425b5513b8SEddie James 	cmd[3] = 1;			/* data length lsb */
1435b5513b8SEddie James 	cmd[4] = occ->poll_cmd_data;	/* data */
144908dbf02SEddie James 	cmd[5] = 0;			/* checksum msb */
145908dbf02SEddie James 	cmd[6] = 0;			/* checksum lsb */
1465b5513b8SEddie James 
147c10e753dSEddie James 	/* mutex should already be locked if necessary */
148d0fef244SEddie James 	rc = occ->send_cmd(occ, cmd, sizeof(cmd), &occ->resp, sizeof(occ->resp));
149df04ced6SEddie James 	if (rc) {
150b5c46a53SEddie James 		occ->last_error = rc;
151df04ced6SEddie James 		if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD)
152df04ced6SEddie James 			occ->error = rc;
153df04ced6SEddie James 
154df04ced6SEddie James 		goto done;
155df04ced6SEddie James 	}
156df04ced6SEddie James 
157df04ced6SEddie James 	/* clear error since communication was successful */
158df04ced6SEddie James 	occ->error_count = 0;
159b5c46a53SEddie James 	occ->last_error = 0;
160df04ced6SEddie James 	occ->error = 0;
161df04ced6SEddie James 
162df04ced6SEddie James 	/* check for safe state */
163df04ced6SEddie James 	header = (struct occ_poll_response_header *)occ->resp.data;
164df04ced6SEddie James 	if (header->occ_state == OCC_STATE_SAFE) {
165df04ced6SEddie James 		if (occ->last_safe) {
166df04ced6SEddie James 			if (time_after(jiffies,
167df04ced6SEddie James 				       occ->last_safe + OCC_SAFE_TIMEOUT))
168df04ced6SEddie James 				occ->error = -EHOSTDOWN;
169df04ced6SEddie James 		} else {
170df04ced6SEddie James 			occ->last_safe = jiffies;
171df04ced6SEddie James 		}
172df04ced6SEddie James 	} else {
173df04ced6SEddie James 		occ->last_safe = 0;
174df04ced6SEddie James 	}
175df04ced6SEddie James 
176df04ced6SEddie James done:
177df04ced6SEddie James 	occ_sysfs_poll_done(occ);
178df04ced6SEddie James 	return rc;
1795b5513b8SEddie James }
1805b5513b8SEddie James 
181c10e753dSEddie James static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
182c10e753dSEddie James {
183c10e753dSEddie James 	int rc;
184c10e753dSEddie James 	u8 cmd[8];
185d0fef244SEddie James 	u8 resp[8];
186c10e753dSEddie James 	__be16 user_power_cap_be = cpu_to_be16(user_power_cap);
187c10e753dSEddie James 
188908dbf02SEddie James 	cmd[0] = 0;	/* sequence number */
189908dbf02SEddie James 	cmd[1] = 0x22;	/* cmd type */
190908dbf02SEddie James 	cmd[2] = 0;	/* data length msb */
191908dbf02SEddie James 	cmd[3] = 2;	/* data length lsb */
192c10e753dSEddie James 
193c10e753dSEddie James 	memcpy(&cmd[4], &user_power_cap_be, 2);
194c10e753dSEddie James 
195908dbf02SEddie James 	cmd[6] = 0;	/* checksum msb */
196908dbf02SEddie James 	cmd[7] = 0;	/* checksum lsb */
197c10e753dSEddie James 
198c10e753dSEddie James 	rc = mutex_lock_interruptible(&occ->lock);
199c10e753dSEddie James 	if (rc)
200c10e753dSEddie James 		return rc;
201c10e753dSEddie James 
202d0fef244SEddie James 	rc = occ->send_cmd(occ, cmd, sizeof(cmd), resp, sizeof(resp));
203c10e753dSEddie James 
204c10e753dSEddie James 	mutex_unlock(&occ->lock);
205c10e753dSEddie James 
206c10e753dSEddie James 	return rc;
207c10e753dSEddie James }
208c10e753dSEddie James 
209df04ced6SEddie James int occ_update_response(struct occ *occ)
210c10e753dSEddie James {
211c10e753dSEddie James 	int rc = mutex_lock_interruptible(&occ->lock);
212c10e753dSEddie James 
213c10e753dSEddie James 	if (rc)
214c10e753dSEddie James 		return rc;
215c10e753dSEddie James 
216c10e753dSEddie James 	/* limit the maximum rate of polling the OCC */
2175216dff2SEddie James 	if (time_after(jiffies, occ->next_update)) {
218c10e753dSEddie James 		rc = occ_poll(occ);
2195216dff2SEddie James 		occ->next_update = jiffies + OCC_UPDATE_FREQUENCY;
220b5c46a53SEddie James 	} else {
221b5c46a53SEddie James 		rc = occ->last_error;
222c10e753dSEddie James 	}
223c10e753dSEddie James 
224c10e753dSEddie James 	mutex_unlock(&occ->lock);
225c10e753dSEddie James 	return rc;
226c10e753dSEddie James }
227c10e753dSEddie James 
228c10e753dSEddie James static ssize_t occ_show_temp_1(struct device *dev,
229c10e753dSEddie James 			       struct device_attribute *attr, char *buf)
230c10e753dSEddie James {
231c10e753dSEddie James 	int rc;
232c10e753dSEddie James 	u32 val = 0;
233c10e753dSEddie James 	struct temp_sensor_1 *temp;
234c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
235c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
236c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
237c10e753dSEddie James 
238c10e753dSEddie James 	rc = occ_update_response(occ);
239c10e753dSEddie James 	if (rc)
240c10e753dSEddie James 		return rc;
241c10e753dSEddie James 
242c10e753dSEddie James 	temp = ((struct temp_sensor_1 *)sensors->temp.data) + sattr->index;
243c10e753dSEddie James 
244c10e753dSEddie James 	switch (sattr->nr) {
245c10e753dSEddie James 	case 0:
246c10e753dSEddie James 		val = get_unaligned_be16(&temp->sensor_id);
247c10e753dSEddie James 		break;
248c10e753dSEddie James 	case 1:
24980830342SAlexander Soldatov 		/*
25080830342SAlexander Soldatov 		 * If a sensor reading has expired and couldn't be refreshed,
25180830342SAlexander Soldatov 		 * OCC returns 0xFFFF for that sensor.
25280830342SAlexander Soldatov 		 */
25380830342SAlexander Soldatov 		if (temp->value == 0xFFFF)
25480830342SAlexander Soldatov 			return -EREMOTEIO;
255c10e753dSEddie James 		val = get_unaligned_be16(&temp->value) * 1000;
256c10e753dSEddie James 		break;
257c10e753dSEddie James 	default:
258c10e753dSEddie James 		return -EINVAL;
259c10e753dSEddie James 	}
260c10e753dSEddie James 
2611f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%u\n", val);
262c10e753dSEddie James }
263c10e753dSEddie James 
264c10e753dSEddie James static ssize_t occ_show_temp_2(struct device *dev,
265c10e753dSEddie James 			       struct device_attribute *attr, char *buf)
266c10e753dSEddie James {
267c10e753dSEddie James 	int rc;
268c10e753dSEddie James 	u32 val = 0;
269c10e753dSEddie James 	struct temp_sensor_2 *temp;
270c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
271c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
272c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
273c10e753dSEddie James 
274c10e753dSEddie James 	rc = occ_update_response(occ);
275c10e753dSEddie James 	if (rc)
276c10e753dSEddie James 		return rc;
277c10e753dSEddie James 
278c10e753dSEddie James 	temp = ((struct temp_sensor_2 *)sensors->temp.data) + sattr->index;
279c10e753dSEddie James 
280c10e753dSEddie James 	switch (sattr->nr) {
281c10e753dSEddie James 	case 0:
282c10e753dSEddie James 		val = get_unaligned_be32(&temp->sensor_id);
283c10e753dSEddie James 		break;
284c10e753dSEddie James 	case 1:
285c10e753dSEddie James 		val = temp->value;
286c10e753dSEddie James 		if (val == OCC_TEMP_SENSOR_FAULT)
287c10e753dSEddie James 			return -EREMOTEIO;
288c10e753dSEddie James 
289c10e753dSEddie James 		/*
290c10e753dSEddie James 		 * VRM doesn't return temperature, only alarm bit. This
291c10e753dSEddie James 		 * attribute maps to tempX_alarm instead of tempX_input for
292c10e753dSEddie James 		 * VRM
293c10e753dSEddie James 		 */
294c10e753dSEddie James 		if (temp->fru_type != OCC_FRU_TYPE_VRM) {
295c10e753dSEddie James 			/* sensor not ready */
296c10e753dSEddie James 			if (val == 0)
297c10e753dSEddie James 				return -EAGAIN;
298c10e753dSEddie James 
299c10e753dSEddie James 			val *= 1000;
300c10e753dSEddie James 		}
301c10e753dSEddie James 		break;
302c10e753dSEddie James 	case 2:
303c10e753dSEddie James 		val = temp->fru_type;
304c10e753dSEddie James 		break;
305c10e753dSEddie James 	case 3:
306c10e753dSEddie James 		val = temp->value == OCC_TEMP_SENSOR_FAULT;
307c10e753dSEddie James 		break;
308c10e753dSEddie James 	default:
309c10e753dSEddie James 		return -EINVAL;
310c10e753dSEddie James 	}
311c10e753dSEddie James 
3121f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%u\n", val);
313c10e753dSEddie James }
314c10e753dSEddie James 
315db4919ecSEddie James static ssize_t occ_show_temp_10(struct device *dev,
316db4919ecSEddie James 				struct device_attribute *attr, char *buf)
317db4919ecSEddie James {
318db4919ecSEddie James 	int rc;
319db4919ecSEddie James 	u32 val = 0;
320db4919ecSEddie James 	struct temp_sensor_10 *temp;
321db4919ecSEddie James 	struct occ *occ = dev_get_drvdata(dev);
322db4919ecSEddie James 	struct occ_sensors *sensors = &occ->sensors;
323db4919ecSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
324db4919ecSEddie James 
325db4919ecSEddie James 	rc = occ_update_response(occ);
326db4919ecSEddie James 	if (rc)
327db4919ecSEddie James 		return rc;
328db4919ecSEddie James 
329db4919ecSEddie James 	temp = ((struct temp_sensor_10 *)sensors->temp.data) + sattr->index;
330db4919ecSEddie James 
331db4919ecSEddie James 	switch (sattr->nr) {
332db4919ecSEddie James 	case 0:
333db4919ecSEddie James 		val = get_unaligned_be32(&temp->sensor_id);
334db4919ecSEddie James 		break;
335db4919ecSEddie James 	case 1:
336db4919ecSEddie James 		val = temp->value;
337db4919ecSEddie James 		if (val == OCC_TEMP_SENSOR_FAULT)
338db4919ecSEddie James 			return -EREMOTEIO;
339db4919ecSEddie James 
340db4919ecSEddie James 		/* sensor not ready */
341db4919ecSEddie James 		if (val == 0)
342db4919ecSEddie James 			return -EAGAIN;
343db4919ecSEddie James 
344db4919ecSEddie James 		val *= 1000;
345db4919ecSEddie James 		break;
346db4919ecSEddie James 	case 2:
347db4919ecSEddie James 		val = temp->fru_type;
348db4919ecSEddie James 		break;
349db4919ecSEddie James 	case 3:
350db4919ecSEddie James 		val = temp->value == OCC_TEMP_SENSOR_FAULT;
351db4919ecSEddie James 		break;
352db4919ecSEddie James 	case 4:
353db4919ecSEddie James 		val = temp->throttle * 1000;
354db4919ecSEddie James 		break;
355db4919ecSEddie James 	default:
356db4919ecSEddie James 		return -EINVAL;
357db4919ecSEddie James 	}
358db4919ecSEddie James 
3591f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%u\n", val);
360db4919ecSEddie James }
361db4919ecSEddie James 
362c10e753dSEddie James static ssize_t occ_show_freq_1(struct device *dev,
363c10e753dSEddie James 			       struct device_attribute *attr, char *buf)
364c10e753dSEddie James {
365c10e753dSEddie James 	int rc;
366c10e753dSEddie James 	u16 val = 0;
367c10e753dSEddie James 	struct freq_sensor_1 *freq;
368c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
369c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
370c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
371c10e753dSEddie James 
372c10e753dSEddie James 	rc = occ_update_response(occ);
373c10e753dSEddie James 	if (rc)
374c10e753dSEddie James 		return rc;
375c10e753dSEddie James 
376c10e753dSEddie James 	freq = ((struct freq_sensor_1 *)sensors->freq.data) + sattr->index;
377c10e753dSEddie James 
378c10e753dSEddie James 	switch (sattr->nr) {
379c10e753dSEddie James 	case 0:
380c10e753dSEddie James 		val = get_unaligned_be16(&freq->sensor_id);
381c10e753dSEddie James 		break;
382c10e753dSEddie James 	case 1:
383c10e753dSEddie James 		val = get_unaligned_be16(&freq->value);
384c10e753dSEddie James 		break;
385c10e753dSEddie James 	default:
386c10e753dSEddie James 		return -EINVAL;
387c10e753dSEddie James 	}
388c10e753dSEddie James 
3891f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%u\n", val);
390c10e753dSEddie James }
391c10e753dSEddie James 
392c10e753dSEddie James static ssize_t occ_show_freq_2(struct device *dev,
393c10e753dSEddie James 			       struct device_attribute *attr, char *buf)
394c10e753dSEddie James {
395c10e753dSEddie James 	int rc;
396c10e753dSEddie James 	u32 val = 0;
397c10e753dSEddie James 	struct freq_sensor_2 *freq;
398c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
399c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
400c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
401c10e753dSEddie James 
402c10e753dSEddie James 	rc = occ_update_response(occ);
403c10e753dSEddie James 	if (rc)
404c10e753dSEddie James 		return rc;
405c10e753dSEddie James 
406c10e753dSEddie James 	freq = ((struct freq_sensor_2 *)sensors->freq.data) + sattr->index;
407c10e753dSEddie James 
408c10e753dSEddie James 	switch (sattr->nr) {
409c10e753dSEddie James 	case 0:
410c10e753dSEddie James 		val = get_unaligned_be32(&freq->sensor_id);
411c10e753dSEddie James 		break;
412c10e753dSEddie James 	case 1:
413c10e753dSEddie James 		val = get_unaligned_be16(&freq->value);
414c10e753dSEddie James 		break;
415c10e753dSEddie James 	default:
416c10e753dSEddie James 		return -EINVAL;
417c10e753dSEddie James 	}
418c10e753dSEddie James 
4191f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%u\n", val);
420c10e753dSEddie James }
421c10e753dSEddie James 
422c10e753dSEddie James static ssize_t occ_show_power_1(struct device *dev,
423c10e753dSEddie James 				struct device_attribute *attr, char *buf)
424c10e753dSEddie James {
425c10e753dSEddie James 	int rc;
426c10e753dSEddie James 	u64 val = 0;
427c10e753dSEddie James 	struct power_sensor_1 *power;
428c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
429c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
430c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
431c10e753dSEddie James 
432c10e753dSEddie James 	rc = occ_update_response(occ);
433c10e753dSEddie James 	if (rc)
434c10e753dSEddie James 		return rc;
435c10e753dSEddie James 
436c10e753dSEddie James 	power = ((struct power_sensor_1 *)sensors->power.data) + sattr->index;
437c10e753dSEddie James 
438c10e753dSEddie James 	switch (sattr->nr) {
439c10e753dSEddie James 	case 0:
440c10e753dSEddie James 		val = get_unaligned_be16(&power->sensor_id);
441c10e753dSEddie James 		break;
442c10e753dSEddie James 	case 1:
443c10e753dSEddie James 		val = get_unaligned_be32(&power->accumulator) /
444c10e753dSEddie James 			get_unaligned_be32(&power->update_tag);
445c10e753dSEddie James 		val *= 1000000ULL;
446c10e753dSEddie James 		break;
447c10e753dSEddie James 	case 2:
448b0407d82SGustavo A. R. Silva 		val = (u64)get_unaligned_be32(&power->update_tag) *
449c10e753dSEddie James 			   occ->powr_sample_time_us;
450c10e753dSEddie James 		break;
451c10e753dSEddie James 	case 3:
452c10e753dSEddie James 		val = get_unaligned_be16(&power->value) * 1000000ULL;
453c10e753dSEddie James 		break;
454c10e753dSEddie James 	default:
455c10e753dSEddie James 		return -EINVAL;
456c10e753dSEddie James 	}
457c10e753dSEddie James 
4581f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%llu\n", val);
459c10e753dSEddie James }
460c10e753dSEddie James 
461c10e753dSEddie James static u64 occ_get_powr_avg(u64 *accum, u32 *samples)
462c10e753dSEddie James {
463211186caSLei YU 	u64 divisor = get_unaligned_be32(samples);
464211186caSLei YU 
465211186caSLei YU 	return (divisor == 0) ? 0 :
466211186caSLei YU 		div64_u64(get_unaligned_be64(accum) * 1000000ULL, divisor);
467c10e753dSEddie James }
468c10e753dSEddie James 
469c10e753dSEddie James static ssize_t occ_show_power_2(struct device *dev,
470c10e753dSEddie James 				struct device_attribute *attr, char *buf)
471c10e753dSEddie James {
472c10e753dSEddie James 	int rc;
473c10e753dSEddie James 	u64 val = 0;
474c10e753dSEddie James 	struct power_sensor_2 *power;
475c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
476c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
477c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
478c10e753dSEddie James 
479c10e753dSEddie James 	rc = occ_update_response(occ);
480c10e753dSEddie James 	if (rc)
481c10e753dSEddie James 		return rc;
482c10e753dSEddie James 
483c10e753dSEddie James 	power = ((struct power_sensor_2 *)sensors->power.data) + sattr->index;
484c10e753dSEddie James 
485c10e753dSEddie James 	switch (sattr->nr) {
486c10e753dSEddie James 	case 0:
4871f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "%u_%u_%u\n",
488c10e753dSEddie James 				  get_unaligned_be32(&power->sensor_id),
489c10e753dSEddie James 				  power->function_id, power->apss_channel);
490c10e753dSEddie James 	case 1:
491c10e753dSEddie James 		val = occ_get_powr_avg(&power->accumulator,
492c10e753dSEddie James 				       &power->update_tag);
493c10e753dSEddie James 		break;
494c10e753dSEddie James 	case 2:
495b0407d82SGustavo A. R. Silva 		val = (u64)get_unaligned_be32(&power->update_tag) *
496c10e753dSEddie James 			   occ->powr_sample_time_us;
497c10e753dSEddie James 		break;
498c10e753dSEddie James 	case 3:
499c10e753dSEddie James 		val = get_unaligned_be16(&power->value) * 1000000ULL;
500c10e753dSEddie James 		break;
501c10e753dSEddie James 	default:
502c10e753dSEddie James 		return -EINVAL;
503c10e753dSEddie James 	}
504c10e753dSEddie James 
5051f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%llu\n", val);
506c10e753dSEddie James }
507c10e753dSEddie James 
508c10e753dSEddie James static ssize_t occ_show_power_a0(struct device *dev,
509c10e753dSEddie James 				 struct device_attribute *attr, char *buf)
510c10e753dSEddie James {
511c10e753dSEddie James 	int rc;
512c10e753dSEddie James 	u64 val = 0;
513c10e753dSEddie James 	struct power_sensor_a0 *power;
514c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
515c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
516c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
517c10e753dSEddie James 
518c10e753dSEddie James 	rc = occ_update_response(occ);
519c10e753dSEddie James 	if (rc)
520c10e753dSEddie James 		return rc;
521c10e753dSEddie James 
522c10e753dSEddie James 	power = ((struct power_sensor_a0 *)sensors->power.data) + sattr->index;
523c10e753dSEddie James 
524c10e753dSEddie James 	switch (sattr->nr) {
525c10e753dSEddie James 	case 0:
5261f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "%u_system\n",
527c10e753dSEddie James 				  get_unaligned_be32(&power->sensor_id));
528c10e753dSEddie James 	case 1:
529c10e753dSEddie James 		val = occ_get_powr_avg(&power->system.accumulator,
530c10e753dSEddie James 				       &power->system.update_tag);
531c10e753dSEddie James 		break;
532c10e753dSEddie James 	case 2:
533b0407d82SGustavo A. R. Silva 		val = (u64)get_unaligned_be32(&power->system.update_tag) *
534c10e753dSEddie James 			   occ->powr_sample_time_us;
535c10e753dSEddie James 		break;
536c10e753dSEddie James 	case 3:
537c10e753dSEddie James 		val = get_unaligned_be16(&power->system.value) * 1000000ULL;
538c10e753dSEddie James 		break;
539c10e753dSEddie James 	case 4:
5401f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "%u_proc\n",
541c10e753dSEddie James 				  get_unaligned_be32(&power->sensor_id));
542c10e753dSEddie James 	case 5:
543c10e753dSEddie James 		val = occ_get_powr_avg(&power->proc.accumulator,
544c10e753dSEddie James 				       &power->proc.update_tag);
545c10e753dSEddie James 		break;
546c10e753dSEddie James 	case 6:
547b0407d82SGustavo A. R. Silva 		val = (u64)get_unaligned_be32(&power->proc.update_tag) *
548c10e753dSEddie James 			   occ->powr_sample_time_us;
549c10e753dSEddie James 		break;
550c10e753dSEddie James 	case 7:
551c10e753dSEddie James 		val = get_unaligned_be16(&power->proc.value) * 1000000ULL;
552c10e753dSEddie James 		break;
553c10e753dSEddie James 	case 8:
5541f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "%u_vdd\n",
555c10e753dSEddie James 				  get_unaligned_be32(&power->sensor_id));
556c10e753dSEddie James 	case 9:
557c10e753dSEddie James 		val = occ_get_powr_avg(&power->vdd.accumulator,
558c10e753dSEddie James 				       &power->vdd.update_tag);
559c10e753dSEddie James 		break;
560c10e753dSEddie James 	case 10:
561b0407d82SGustavo A. R. Silva 		val = (u64)get_unaligned_be32(&power->vdd.update_tag) *
562c10e753dSEddie James 			   occ->powr_sample_time_us;
563c10e753dSEddie James 		break;
564c10e753dSEddie James 	case 11:
565c10e753dSEddie James 		val = get_unaligned_be16(&power->vdd.value) * 1000000ULL;
566c10e753dSEddie James 		break;
567c10e753dSEddie James 	case 12:
5681f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "%u_vdn\n",
569c10e753dSEddie James 				  get_unaligned_be32(&power->sensor_id));
570c10e753dSEddie James 	case 13:
571c10e753dSEddie James 		val = occ_get_powr_avg(&power->vdn.accumulator,
572c10e753dSEddie James 				       &power->vdn.update_tag);
573c10e753dSEddie James 		break;
574c10e753dSEddie James 	case 14:
575b0407d82SGustavo A. R. Silva 		val = (u64)get_unaligned_be32(&power->vdn.update_tag) *
576c10e753dSEddie James 			   occ->powr_sample_time_us;
577c10e753dSEddie James 		break;
578c10e753dSEddie James 	case 15:
579c10e753dSEddie James 		val = get_unaligned_be16(&power->vdn.value) * 1000000ULL;
580c10e753dSEddie James 		break;
581c10e753dSEddie James 	default:
582c10e753dSEddie James 		return -EINVAL;
583c10e753dSEddie James 	}
584c10e753dSEddie James 
5851f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%llu\n", val);
586c10e753dSEddie James }
587c10e753dSEddie James 
588c10e753dSEddie James static ssize_t occ_show_caps_1_2(struct device *dev,
589c10e753dSEddie James 				 struct device_attribute *attr, char *buf)
590c10e753dSEddie James {
591c10e753dSEddie James 	int rc;
592c10e753dSEddie James 	u64 val = 0;
593c10e753dSEddie James 	struct caps_sensor_2 *caps;
594c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
595c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
596c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
597c10e753dSEddie James 
598c10e753dSEddie James 	rc = occ_update_response(occ);
599c10e753dSEddie James 	if (rc)
600c10e753dSEddie James 		return rc;
601c10e753dSEddie James 
602c10e753dSEddie James 	caps = ((struct caps_sensor_2 *)sensors->caps.data) + sattr->index;
603c10e753dSEddie James 
604c10e753dSEddie James 	switch (sattr->nr) {
605c10e753dSEddie James 	case 0:
6061f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "system\n");
607c10e753dSEddie James 	case 1:
608c10e753dSEddie James 		val = get_unaligned_be16(&caps->cap) * 1000000ULL;
609c10e753dSEddie James 		break;
610c10e753dSEddie James 	case 2:
611c10e753dSEddie James 		val = get_unaligned_be16(&caps->system_power) * 1000000ULL;
612c10e753dSEddie James 		break;
613c10e753dSEddie James 	case 3:
614c10e753dSEddie James 		val = get_unaligned_be16(&caps->n_cap) * 1000000ULL;
615c10e753dSEddie James 		break;
616c10e753dSEddie James 	case 4:
617c10e753dSEddie James 		val = get_unaligned_be16(&caps->max) * 1000000ULL;
618c10e753dSEddie James 		break;
619c10e753dSEddie James 	case 5:
620c10e753dSEddie James 		val = get_unaligned_be16(&caps->min) * 1000000ULL;
621c10e753dSEddie James 		break;
622c10e753dSEddie James 	case 6:
623c10e753dSEddie James 		val = get_unaligned_be16(&caps->user) * 1000000ULL;
624c10e753dSEddie James 		break;
625c10e753dSEddie James 	case 7:
626c10e753dSEddie James 		if (occ->sensors.caps.version == 1)
627c10e753dSEddie James 			return -EINVAL;
628c10e753dSEddie James 
629c10e753dSEddie James 		val = caps->user_source;
630c10e753dSEddie James 		break;
631c10e753dSEddie James 	default:
632c10e753dSEddie James 		return -EINVAL;
633c10e753dSEddie James 	}
634c10e753dSEddie James 
6351f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%llu\n", val);
636c10e753dSEddie James }
637c10e753dSEddie James 
638c10e753dSEddie James static ssize_t occ_show_caps_3(struct device *dev,
639c10e753dSEddie James 			       struct device_attribute *attr, char *buf)
640c10e753dSEddie James {
641c10e753dSEddie James 	int rc;
642c10e753dSEddie James 	u64 val = 0;
643c10e753dSEddie James 	struct caps_sensor_3 *caps;
644c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
645c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
646c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
647c10e753dSEddie James 
648c10e753dSEddie James 	rc = occ_update_response(occ);
649c10e753dSEddie James 	if (rc)
650c10e753dSEddie James 		return rc;
651c10e753dSEddie James 
652c10e753dSEddie James 	caps = ((struct caps_sensor_3 *)sensors->caps.data) + sattr->index;
653c10e753dSEddie James 
654c10e753dSEddie James 	switch (sattr->nr) {
655c10e753dSEddie James 	case 0:
6561f4d4af4SGuenter Roeck 		return sysfs_emit(buf, "system\n");
657c10e753dSEddie James 	case 1:
658c10e753dSEddie James 		val = get_unaligned_be16(&caps->cap) * 1000000ULL;
659c10e753dSEddie James 		break;
660c10e753dSEddie James 	case 2:
661c10e753dSEddie James 		val = get_unaligned_be16(&caps->system_power) * 1000000ULL;
662c10e753dSEddie James 		break;
663c10e753dSEddie James 	case 3:
664c10e753dSEddie James 		val = get_unaligned_be16(&caps->n_cap) * 1000000ULL;
665c10e753dSEddie James 		break;
666c10e753dSEddie James 	case 4:
667c10e753dSEddie James 		val = get_unaligned_be16(&caps->max) * 1000000ULL;
668c10e753dSEddie James 		break;
669c10e753dSEddie James 	case 5:
670c10e753dSEddie James 		val = get_unaligned_be16(&caps->hard_min) * 1000000ULL;
671c10e753dSEddie James 		break;
672c10e753dSEddie James 	case 6:
673c10e753dSEddie James 		val = get_unaligned_be16(&caps->user) * 1000000ULL;
674c10e753dSEddie James 		break;
675c10e753dSEddie James 	case 7:
676c10e753dSEddie James 		val = caps->user_source;
677c10e753dSEddie James 		break;
6782b8d17ddSEddie James 	case 8:
6792b8d17ddSEddie James 		val = get_unaligned_be16(&caps->soft_min) * 1000000ULL;
6802b8d17ddSEddie James 		break;
681c10e753dSEddie James 	default:
682c10e753dSEddie James 		return -EINVAL;
683c10e753dSEddie James 	}
684c10e753dSEddie James 
6851f4d4af4SGuenter Roeck 	return sysfs_emit(buf, "%llu\n", val);
686c10e753dSEddie James }
687c10e753dSEddie James 
688c10e753dSEddie James static ssize_t occ_store_caps_user(struct device *dev,
689c10e753dSEddie James 				   struct device_attribute *attr,
690c10e753dSEddie James 				   const char *buf, size_t count)
691c10e753dSEddie James {
692c10e753dSEddie James 	int rc;
693c10e753dSEddie James 	u16 user_power_cap;
694c10e753dSEddie James 	unsigned long long value;
695c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
696c10e753dSEddie James 
697c10e753dSEddie James 	rc = kstrtoull(buf, 0, &value);
698c10e753dSEddie James 	if (rc)
699c10e753dSEddie James 		return rc;
700c10e753dSEddie James 
701c10e753dSEddie James 	user_power_cap = div64_u64(value, 1000000ULL); /* microwatt to watt */
702c10e753dSEddie James 
703c10e753dSEddie James 	rc = occ_set_user_power_cap(occ, user_power_cap);
704c10e753dSEddie James 	if (rc)
705c10e753dSEddie James 		return rc;
706c10e753dSEddie James 
707c10e753dSEddie James 	return count;
708c10e753dSEddie James }
709c10e753dSEddie James 
710c10e753dSEddie James static ssize_t occ_show_extended(struct device *dev,
711c10e753dSEddie James 				 struct device_attribute *attr, char *buf)
712c10e753dSEddie James {
713c10e753dSEddie James 	int rc;
714c10e753dSEddie James 	struct extended_sensor *extn;
715c10e753dSEddie James 	struct occ *occ = dev_get_drvdata(dev);
716c10e753dSEddie James 	struct occ_sensors *sensors = &occ->sensors;
717c10e753dSEddie James 	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
718c10e753dSEddie James 
719c10e753dSEddie James 	rc = occ_update_response(occ);
720c10e753dSEddie James 	if (rc)
721c10e753dSEddie James 		return rc;
722c10e753dSEddie James 
723c10e753dSEddie James 	extn = ((struct extended_sensor *)sensors->extended.data) +
724c10e753dSEddie James 		sattr->index;
725c10e753dSEddie James 
726c10e753dSEddie James 	switch (sattr->nr) {
727c10e753dSEddie James 	case 0:
7281f4d4af4SGuenter Roeck 		if (extn->flags & EXTN_FLAG_SENSOR_ID) {
7291f4d4af4SGuenter Roeck 			rc = sysfs_emit(buf, "%u",
730c10e753dSEddie James 					get_unaligned_be32(&extn->sensor_id));
7311f4d4af4SGuenter Roeck 		} else {
7321f4d4af4SGuenter Roeck 			rc = sysfs_emit(buf, "%02x%02x%02x%02x\n",
733c10e753dSEddie James 					extn->name[0], extn->name[1],
734c10e753dSEddie James 					extn->name[2], extn->name[3]);
7351f4d4af4SGuenter Roeck 		}
736c10e753dSEddie James 		break;
737c10e753dSEddie James 	case 1:
7381f4d4af4SGuenter Roeck 		rc = sysfs_emit(buf, "%02x\n", extn->flags);
739c10e753dSEddie James 		break;
740c10e753dSEddie James 	case 2:
7411f4d4af4SGuenter Roeck 		rc = sysfs_emit(buf, "%02x%02x%02x%02x%02x%02x\n",
742c10e753dSEddie James 				extn->data[0], extn->data[1], extn->data[2],
743c10e753dSEddie James 				extn->data[3], extn->data[4], extn->data[5]);
744c10e753dSEddie James 		break;
745c10e753dSEddie James 	default:
746c10e753dSEddie James 		return -EINVAL;
747c10e753dSEddie James 	}
748c10e753dSEddie James 
749c10e753dSEddie James 	return rc;
750c10e753dSEddie James }
751c10e753dSEddie James 
75254076cb3SEddie James /*
75354076cb3SEddie James  * Some helper macros to make it easier to define an occ_attribute. Since these
75454076cb3SEddie James  * are dynamically allocated, we shouldn't use the existing kernel macros which
75554076cb3SEddie James  * stringify the name argument.
75654076cb3SEddie James  */
75754076cb3SEddie James #define ATTR_OCC(_name, _mode, _show, _store) {				\
75854076cb3SEddie James 	.attr	= {							\
75954076cb3SEddie James 		.name = _name,						\
76054076cb3SEddie James 		.mode = VERIFY_OCTAL_PERMISSIONS(_mode),		\
76154076cb3SEddie James 	},								\
76254076cb3SEddie James 	.show	= _show,						\
76354076cb3SEddie James 	.store	= _store,						\
76454076cb3SEddie James }
76554076cb3SEddie James 
76654076cb3SEddie James #define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) {	\
76754076cb3SEddie James 	.dev_attr	= ATTR_OCC(_name, _mode, _show, _store),	\
76854076cb3SEddie James 	.index		= _index,					\
76954076cb3SEddie James 	.nr		= _nr,						\
77054076cb3SEddie James }
77154076cb3SEddie James 
77254076cb3SEddie James #define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index)		\
77354076cb3SEddie James 	((struct sensor_device_attribute_2)				\
77454076cb3SEddie James 		SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index))
77554076cb3SEddie James 
77654076cb3SEddie James /*
77754076cb3SEddie James  * Allocate and instatiate sensor_device_attribute_2s. It's most efficient to
77854076cb3SEddie James  * use our own instead of the built-in hwmon attribute types.
77954076cb3SEddie James  */
78054076cb3SEddie James static int occ_setup_sensor_attrs(struct occ *occ)
78154076cb3SEddie James {
78254076cb3SEddie James 	unsigned int i, s, num_attrs = 0;
78354076cb3SEddie James 	struct device *dev = occ->bus_dev;
78454076cb3SEddie James 	struct occ_sensors *sensors = &occ->sensors;
78554076cb3SEddie James 	struct occ_attribute *attr;
78654076cb3SEddie James 	struct temp_sensor_2 *temp;
78754076cb3SEddie James 	ssize_t (*show_temp)(struct device *, struct device_attribute *,
78854076cb3SEddie James 			     char *) = occ_show_temp_1;
78954076cb3SEddie James 	ssize_t (*show_freq)(struct device *, struct device_attribute *,
79054076cb3SEddie James 			     char *) = occ_show_freq_1;
79154076cb3SEddie James 	ssize_t (*show_power)(struct device *, struct device_attribute *,
79254076cb3SEddie James 			      char *) = occ_show_power_1;
79354076cb3SEddie James 	ssize_t (*show_caps)(struct device *, struct device_attribute *,
79454076cb3SEddie James 			     char *) = occ_show_caps_1_2;
79554076cb3SEddie James 
79654076cb3SEddie James 	switch (sensors->temp.version) {
79754076cb3SEddie James 	case 1:
79854076cb3SEddie James 		num_attrs += (sensors->temp.num_sensors * 2);
79954076cb3SEddie James 		break;
80054076cb3SEddie James 	case 2:
80154076cb3SEddie James 		num_attrs += (sensors->temp.num_sensors * 4);
80254076cb3SEddie James 		show_temp = occ_show_temp_2;
80354076cb3SEddie James 		break;
804db4919ecSEddie James 	case 0x10:
805db4919ecSEddie James 		num_attrs += (sensors->temp.num_sensors * 5);
806db4919ecSEddie James 		show_temp = occ_show_temp_10;
807db4919ecSEddie James 		break;
80854076cb3SEddie James 	default:
80954076cb3SEddie James 		sensors->temp.num_sensors = 0;
81054076cb3SEddie James 	}
81154076cb3SEddie James 
81254076cb3SEddie James 	switch (sensors->freq.version) {
81354076cb3SEddie James 	case 2:
81454076cb3SEddie James 		show_freq = occ_show_freq_2;
815df561f66SGustavo A. R. Silva 		fallthrough;
81654076cb3SEddie James 	case 1:
81754076cb3SEddie James 		num_attrs += (sensors->freq.num_sensors * 2);
81854076cb3SEddie James 		break;
81954076cb3SEddie James 	default:
82054076cb3SEddie James 		sensors->freq.num_sensors = 0;
82154076cb3SEddie James 	}
82254076cb3SEddie James 
82354076cb3SEddie James 	switch (sensors->power.version) {
82454076cb3SEddie James 	case 2:
82554076cb3SEddie James 		show_power = occ_show_power_2;
826df561f66SGustavo A. R. Silva 		fallthrough;
82754076cb3SEddie James 	case 1:
82854076cb3SEddie James 		num_attrs += (sensors->power.num_sensors * 4);
82954076cb3SEddie James 		break;
83054076cb3SEddie James 	case 0xA0:
83154076cb3SEddie James 		num_attrs += (sensors->power.num_sensors * 16);
83254076cb3SEddie James 		show_power = occ_show_power_a0;
83354076cb3SEddie James 		break;
83454076cb3SEddie James 	default:
83554076cb3SEddie James 		sensors->power.num_sensors = 0;
83654076cb3SEddie James 	}
83754076cb3SEddie James 
83854076cb3SEddie James 	switch (sensors->caps.version) {
83954076cb3SEddie James 	case 1:
84054076cb3SEddie James 		num_attrs += (sensors->caps.num_sensors * 7);
84154076cb3SEddie James 		break;
84254076cb3SEddie James 	case 2:
84354076cb3SEddie James 		num_attrs += (sensors->caps.num_sensors * 8);
84454076cb3SEddie James 		break;
8452b8d17ddSEddie James 	case 3:
8462b8d17ddSEddie James 		show_caps = occ_show_caps_3;
8472b8d17ddSEddie James 		num_attrs += (sensors->caps.num_sensors * 9);
8482b8d17ddSEddie James 		break;
84954076cb3SEddie James 	default:
85054076cb3SEddie James 		sensors->caps.num_sensors = 0;
85154076cb3SEddie James 	}
85254076cb3SEddie James 
85354076cb3SEddie James 	switch (sensors->extended.version) {
85454076cb3SEddie James 	case 1:
85554076cb3SEddie James 		num_attrs += (sensors->extended.num_sensors * 3);
85654076cb3SEddie James 		break;
85754076cb3SEddie James 	default:
85854076cb3SEddie James 		sensors->extended.num_sensors = 0;
85954076cb3SEddie James 	}
86054076cb3SEddie James 
86154076cb3SEddie James 	occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * num_attrs,
86254076cb3SEddie James 				  GFP_KERNEL);
86354076cb3SEddie James 	if (!occ->attrs)
86454076cb3SEddie James 		return -ENOMEM;
86554076cb3SEddie James 
86654076cb3SEddie James 	/* null-terminated list */
86754076cb3SEddie James 	occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) *
86854076cb3SEddie James 					num_attrs + 1, GFP_KERNEL);
86954076cb3SEddie James 	if (!occ->group.attrs)
87054076cb3SEddie James 		return -ENOMEM;
87154076cb3SEddie James 
87254076cb3SEddie James 	attr = occ->attrs;
87354076cb3SEddie James 
87454076cb3SEddie James 	for (i = 0; i < sensors->temp.num_sensors; ++i) {
87554076cb3SEddie James 		s = i + 1;
87654076cb3SEddie James 		temp = ((struct temp_sensor_2 *)sensors->temp.data) + i;
87754076cb3SEddie James 
87854076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "temp%d_label", s);
87954076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
88054076cb3SEddie James 					     0, i);
88154076cb3SEddie James 		attr++;
88254076cb3SEddie James 
883ffa26000SEddie James 		if (sensors->temp.version == 2 &&
88454076cb3SEddie James 		    temp->fru_type == OCC_FRU_TYPE_VRM) {
88554076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
88654076cb3SEddie James 				 "temp%d_alarm", s);
88754076cb3SEddie James 		} else {
88854076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
88954076cb3SEddie James 				 "temp%d_input", s);
89054076cb3SEddie James 		}
89154076cb3SEddie James 
89254076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
89354076cb3SEddie James 					     1, i);
89454076cb3SEddie James 		attr++;
89554076cb3SEddie James 
89654076cb3SEddie James 		if (sensors->temp.version > 1) {
89754076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
89854076cb3SEddie James 				 "temp%d_fru_type", s);
89954076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
90054076cb3SEddie James 						     show_temp, NULL, 2, i);
90154076cb3SEddie James 			attr++;
90254076cb3SEddie James 
90354076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
90454076cb3SEddie James 				 "temp%d_fault", s);
90554076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
90654076cb3SEddie James 						     show_temp, NULL, 3, i);
90754076cb3SEddie James 			attr++;
908db4919ecSEddie James 
909db4919ecSEddie James 			if (sensors->temp.version == 0x10) {
910db4919ecSEddie James 				snprintf(attr->name, sizeof(attr->name),
911db4919ecSEddie James 					 "temp%d_max", s);
912db4919ecSEddie James 				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
913db4919ecSEddie James 							     show_temp, NULL,
914db4919ecSEddie James 							     4, i);
915db4919ecSEddie James 				attr++;
916db4919ecSEddie James 			}
91754076cb3SEddie James 		}
91854076cb3SEddie James 	}
91954076cb3SEddie James 
92054076cb3SEddie James 	for (i = 0; i < sensors->freq.num_sensors; ++i) {
92154076cb3SEddie James 		s = i + 1;
92254076cb3SEddie James 
92354076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "freq%d_label", s);
92454076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
92554076cb3SEddie James 					     0, i);
92654076cb3SEddie James 		attr++;
92754076cb3SEddie James 
92854076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "freq%d_input", s);
92954076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
93054076cb3SEddie James 					     1, i);
93154076cb3SEddie James 		attr++;
93254076cb3SEddie James 	}
93354076cb3SEddie James 
93454076cb3SEddie James 	if (sensors->power.version == 0xA0) {
93554076cb3SEddie James 		/*
93654076cb3SEddie James 		 * Special case for many-attribute power sensor. Split it into
93754076cb3SEddie James 		 * a sensor number per power type, emulating several sensors.
93854076cb3SEddie James 		 */
93954076cb3SEddie James 		for (i = 0; i < sensors->power.num_sensors; ++i) {
94054076cb3SEddie James 			unsigned int j;
94154076cb3SEddie James 			unsigned int nr = 0;
94254076cb3SEddie James 
94354076cb3SEddie James 			s = (i * 4) + 1;
94454076cb3SEddie James 
94554076cb3SEddie James 			for (j = 0; j < 4; ++j) {
94654076cb3SEddie James 				snprintf(attr->name, sizeof(attr->name),
94754076cb3SEddie James 					 "power%d_label", s);
94854076cb3SEddie James 				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
94954076cb3SEddie James 							     show_power, NULL,
95054076cb3SEddie James 							     nr++, i);
95154076cb3SEddie James 				attr++;
95254076cb3SEddie James 
95354076cb3SEddie James 				snprintf(attr->name, sizeof(attr->name),
95454076cb3SEddie James 					 "power%d_average", s);
95554076cb3SEddie James 				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
95654076cb3SEddie James 							     show_power, NULL,
95754076cb3SEddie James 							     nr++, i);
95854076cb3SEddie James 				attr++;
95954076cb3SEddie James 
96054076cb3SEddie James 				snprintf(attr->name, sizeof(attr->name),
96154076cb3SEddie James 					 "power%d_average_interval", s);
96254076cb3SEddie James 				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
96354076cb3SEddie James 							     show_power, NULL,
96454076cb3SEddie James 							     nr++, i);
96554076cb3SEddie James 				attr++;
96654076cb3SEddie James 
96754076cb3SEddie James 				snprintf(attr->name, sizeof(attr->name),
96854076cb3SEddie James 					 "power%d_input", s);
96954076cb3SEddie James 				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
97054076cb3SEddie James 							     show_power, NULL,
97154076cb3SEddie James 							     nr++, i);
97254076cb3SEddie James 				attr++;
97354076cb3SEddie James 
97454076cb3SEddie James 				s++;
97554076cb3SEddie James 			}
97654076cb3SEddie James 		}
9778e6af454SEddie James 
9788e6af454SEddie James 		s = (sensors->power.num_sensors * 4) + 1;
97954076cb3SEddie James 	} else {
98054076cb3SEddie James 		for (i = 0; i < sensors->power.num_sensors; ++i) {
98154076cb3SEddie James 			s = i + 1;
98254076cb3SEddie James 
98354076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
98454076cb3SEddie James 				 "power%d_label", s);
98554076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
98654076cb3SEddie James 						     show_power, NULL, 0, i);
98754076cb3SEddie James 			attr++;
98854076cb3SEddie James 
98954076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
99054076cb3SEddie James 				 "power%d_average", s);
99154076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
99254076cb3SEddie James 						     show_power, NULL, 1, i);
99354076cb3SEddie James 			attr++;
99454076cb3SEddie James 
99554076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
99654076cb3SEddie James 				 "power%d_average_interval", s);
99754076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
99854076cb3SEddie James 						     show_power, NULL, 2, i);
99954076cb3SEddie James 			attr++;
100054076cb3SEddie James 
100154076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
100254076cb3SEddie James 				 "power%d_input", s);
100354076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
100454076cb3SEddie James 						     show_power, NULL, 3, i);
100554076cb3SEddie James 			attr++;
100654076cb3SEddie James 		}
10078e6af454SEddie James 
10088e6af454SEddie James 		s = sensors->power.num_sensors + 1;
100954076cb3SEddie James 	}
101054076cb3SEddie James 
101154076cb3SEddie James 	if (sensors->caps.num_sensors >= 1) {
101254076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "power%d_label", s);
101354076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
101454076cb3SEddie James 					     0, 0);
101554076cb3SEddie James 		attr++;
101654076cb3SEddie James 
101754076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "power%d_cap", s);
101854076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
101954076cb3SEddie James 					     1, 0);
102054076cb3SEddie James 		attr++;
102154076cb3SEddie James 
102254076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "power%d_input", s);
102354076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
102454076cb3SEddie James 					     2, 0);
102554076cb3SEddie James 		attr++;
102654076cb3SEddie James 
102754076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name),
102854076cb3SEddie James 			 "power%d_cap_not_redundant", s);
102954076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
103054076cb3SEddie James 					     3, 0);
103154076cb3SEddie James 		attr++;
103254076cb3SEddie James 
103354076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "power%d_cap_max", s);
103454076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
103554076cb3SEddie James 					     4, 0);
103654076cb3SEddie James 		attr++;
103754076cb3SEddie James 
103854076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "power%d_cap_min", s);
103954076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
104054076cb3SEddie James 					     5, 0);
104154076cb3SEddie James 		attr++;
104254076cb3SEddie James 
104354076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "power%d_cap_user",
104454076cb3SEddie James 			 s);
104554076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps,
104654076cb3SEddie James 					     occ_store_caps_user, 6, 0);
104754076cb3SEddie James 		attr++;
104854076cb3SEddie James 
104954076cb3SEddie James 		if (sensors->caps.version > 1) {
105054076cb3SEddie James 			snprintf(attr->name, sizeof(attr->name),
105154076cb3SEddie James 				 "power%d_cap_user_source", s);
105254076cb3SEddie James 			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
105354076cb3SEddie James 						     show_caps, NULL, 7, 0);
105454076cb3SEddie James 			attr++;
10552b8d17ddSEddie James 
10562b8d17ddSEddie James 			if (sensors->caps.version > 2) {
10572b8d17ddSEddie James 				snprintf(attr->name, sizeof(attr->name),
10582b8d17ddSEddie James 					 "power%d_cap_min_soft", s);
10592b8d17ddSEddie James 				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
10602b8d17ddSEddie James 							     show_caps, NULL,
10612b8d17ddSEddie James 							     8, 0);
10622b8d17ddSEddie James 				attr++;
10632b8d17ddSEddie James 			}
106454076cb3SEddie James 		}
106554076cb3SEddie James 	}
106654076cb3SEddie James 
106754076cb3SEddie James 	for (i = 0; i < sensors->extended.num_sensors; ++i) {
106854076cb3SEddie James 		s = i + 1;
106954076cb3SEddie James 
107054076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "extn%d_label", s);
107154076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
107254076cb3SEddie James 					     occ_show_extended, NULL, 0, i);
107354076cb3SEddie James 		attr++;
107454076cb3SEddie James 
107554076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "extn%d_flags", s);
107654076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
107754076cb3SEddie James 					     occ_show_extended, NULL, 1, i);
107854076cb3SEddie James 		attr++;
107954076cb3SEddie James 
108054076cb3SEddie James 		snprintf(attr->name, sizeof(attr->name), "extn%d_input", s);
108154076cb3SEddie James 		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
108254076cb3SEddie James 					     occ_show_extended, NULL, 2, i);
108354076cb3SEddie James 		attr++;
108454076cb3SEddie James 	}
108554076cb3SEddie James 
108654076cb3SEddie James 	/* put the sensors in the group */
108754076cb3SEddie James 	for (i = 0; i < num_attrs; ++i) {
108854076cb3SEddie James 		sysfs_attr_init(&occ->attrs[i].sensor.dev_attr.attr);
108954076cb3SEddie James 		occ->group.attrs[i] = &occ->attrs[i].sensor.dev_attr.attr;
109054076cb3SEddie James 	}
109154076cb3SEddie James 
109254076cb3SEddie James 	return 0;
109354076cb3SEddie James }
109454076cb3SEddie James 
1095aa195fe4SEddie James /* only need to do this once at startup, as OCC won't change sensors on us */
1096aa195fe4SEddie James static void occ_parse_poll_response(struct occ *occ)
1097aa195fe4SEddie James {
1098aa195fe4SEddie James 	unsigned int i, old_offset, offset = 0, size = 0;
1099aa195fe4SEddie James 	struct occ_sensor *sensor;
1100aa195fe4SEddie James 	struct occ_sensors *sensors = &occ->sensors;
1101aa195fe4SEddie James 	struct occ_response *resp = &occ->resp;
1102aa195fe4SEddie James 	struct occ_poll_response *poll =
1103aa195fe4SEddie James 		(struct occ_poll_response *)&resp->data[0];
1104aa195fe4SEddie James 	struct occ_poll_response_header *header = &poll->header;
1105aa195fe4SEddie James 	struct occ_sensor_data_block *block = &poll->block;
1106aa195fe4SEddie James 
1107aa195fe4SEddie James 	dev_info(occ->bus_dev, "OCC found, code level: %.16s\n",
1108aa195fe4SEddie James 		 header->occ_code_level);
1109aa195fe4SEddie James 
1110aa195fe4SEddie James 	for (i = 0; i < header->num_sensor_data_blocks; ++i) {
1111aa195fe4SEddie James 		block = (struct occ_sensor_data_block *)((u8 *)block + offset);
1112aa195fe4SEddie James 		old_offset = offset;
1113aa195fe4SEddie James 		offset = (block->header.num_sensors *
1114aa195fe4SEddie James 			  block->header.sensor_length) + sizeof(block->header);
1115aa195fe4SEddie James 		size += offset;
1116aa195fe4SEddie James 
1117aa195fe4SEddie James 		/* validate all the length/size fields */
1118aa195fe4SEddie James 		if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) {
1119aa195fe4SEddie James 			dev_warn(occ->bus_dev, "exceeded response buffer\n");
1120aa195fe4SEddie James 			return;
1121aa195fe4SEddie James 		}
1122aa195fe4SEddie James 
1123aa195fe4SEddie James 		dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n",
1124aa195fe4SEddie James 			old_offset, offset - 1, block->header.eye_catcher,
1125aa195fe4SEddie James 			block->header.num_sensors);
1126aa195fe4SEddie James 
1127aa195fe4SEddie James 		/* match sensor block type */
1128aa195fe4SEddie James 		if (strncmp(block->header.eye_catcher, "TEMP", 4) == 0)
1129aa195fe4SEddie James 			sensor = &sensors->temp;
1130aa195fe4SEddie James 		else if (strncmp(block->header.eye_catcher, "FREQ", 4) == 0)
1131aa195fe4SEddie James 			sensor = &sensors->freq;
1132aa195fe4SEddie James 		else if (strncmp(block->header.eye_catcher, "POWR", 4) == 0)
1133aa195fe4SEddie James 			sensor = &sensors->power;
1134aa195fe4SEddie James 		else if (strncmp(block->header.eye_catcher, "CAPS", 4) == 0)
1135aa195fe4SEddie James 			sensor = &sensors->caps;
1136aa195fe4SEddie James 		else if (strncmp(block->header.eye_catcher, "EXTN", 4) == 0)
1137aa195fe4SEddie James 			sensor = &sensors->extended;
1138aa195fe4SEddie James 		else {
1139aa195fe4SEddie James 			dev_warn(occ->bus_dev, "sensor not supported %.4s\n",
1140aa195fe4SEddie James 				 block->header.eye_catcher);
1141aa195fe4SEddie James 			continue;
1142aa195fe4SEddie James 		}
1143aa195fe4SEddie James 
1144aa195fe4SEddie James 		sensor->num_sensors = block->header.num_sensors;
1145aa195fe4SEddie James 		sensor->version = block->header.sensor_format;
1146aa195fe4SEddie James 		sensor->data = &block->data;
1147aa195fe4SEddie James 	}
1148aa195fe4SEddie James 
1149aa195fe4SEddie James 	dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", size,
1150aa195fe4SEddie James 		sizeof(*header), size + sizeof(*header));
1151aa195fe4SEddie James }
1152aa195fe4SEddie James 
1153cf8e9f21SEddie James int occ_active(struct occ *occ, bool active)
11545b5513b8SEddie James {
1155cf8e9f21SEddie James 	int rc = mutex_lock_interruptible(&occ->lock);
11565b5513b8SEddie James 
1157cf8e9f21SEddie James 	if (rc)
11585b5513b8SEddie James 		return rc;
1159cf8e9f21SEddie James 
1160cf8e9f21SEddie James 	if (active) {
1161cf8e9f21SEddie James 		if (occ->active) {
1162cf8e9f21SEddie James 			rc = -EALREADY;
1163cf8e9f21SEddie James 			goto unlock;
1164cf8e9f21SEddie James 		}
1165cf8e9f21SEddie James 
1166cf8e9f21SEddie James 		occ->error_count = 0;
1167cf8e9f21SEddie James 		occ->last_safe = 0;
1168cf8e9f21SEddie James 
1169cf8e9f21SEddie James 		rc = occ_poll(occ);
1170cf8e9f21SEddie James 		if (rc < 0) {
117138483e8fSEddie James 			dev_err(occ->bus_dev,
117238483e8fSEddie James 				"failed to get OCC poll response=%02x: %d\n",
117338483e8fSEddie James 				occ->resp.return_status, rc);
1174cf8e9f21SEddie James 			goto unlock;
11755b5513b8SEddie James 		}
11765b5513b8SEddie James 
1177cf8e9f21SEddie James 		occ->active = true;
11785216dff2SEddie James 		occ->next_update = jiffies + OCC_UPDATE_FREQUENCY;
1179aa195fe4SEddie James 		occ_parse_poll_response(occ);
1180aa195fe4SEddie James 
118154076cb3SEddie James 		rc = occ_setup_sensor_attrs(occ);
118254076cb3SEddie James 		if (rc) {
1183cf8e9f21SEddie James 			dev_err(occ->bus_dev,
1184cf8e9f21SEddie James 				"failed to setup sensor attrs: %d\n", rc);
1185cf8e9f21SEddie James 			goto unlock;
1186cf8e9f21SEddie James 		}
1187cf8e9f21SEddie James 
1188cf8e9f21SEddie James 		occ->hwmon = hwmon_device_register_with_groups(occ->bus_dev,
1189cf8e9f21SEddie James 							       "occ", occ,
1190cf8e9f21SEddie James 							       occ->groups);
1191cf8e9f21SEddie James 		if (IS_ERR(occ->hwmon)) {
1192cf8e9f21SEddie James 			rc = PTR_ERR(occ->hwmon);
1193cf8e9f21SEddie James 			occ->hwmon = NULL;
1194cf8e9f21SEddie James 			dev_err(occ->bus_dev,
1195cf8e9f21SEddie James 				"failed to register hwmon device: %d\n", rc);
1196cf8e9f21SEddie James 			goto unlock;
1197cf8e9f21SEddie James 		}
1198cf8e9f21SEddie James 	} else {
1199cf8e9f21SEddie James 		if (!occ->active) {
1200cf8e9f21SEddie James 			rc = -EALREADY;
1201cf8e9f21SEddie James 			goto unlock;
1202cf8e9f21SEddie James 		}
1203cf8e9f21SEddie James 
1204cf8e9f21SEddie James 		if (occ->hwmon)
1205cf8e9f21SEddie James 			hwmon_device_unregister(occ->hwmon);
1206cf8e9f21SEddie James 		occ->active = false;
1207cf8e9f21SEddie James 		occ->hwmon = NULL;
1208cf8e9f21SEddie James 	}
1209cf8e9f21SEddie James 
1210cf8e9f21SEddie James unlock:
1211cf8e9f21SEddie James 	mutex_unlock(&occ->lock);
121254076cb3SEddie James 	return rc;
121354076cb3SEddie James }
121454076cb3SEddie James 
1215cf8e9f21SEddie James int occ_setup(struct occ *occ)
1216cf8e9f21SEddie James {
1217cf8e9f21SEddie James 	int rc;
1218cf8e9f21SEddie James 
1219cf8e9f21SEddie James 	mutex_init(&occ->lock);
1220cf8e9f21SEddie James 	occ->groups[0] = &occ->group;
122154076cb3SEddie James 
1222df04ced6SEddie James 	rc = occ_setup_sysfs(occ);
1223df04ced6SEddie James 	if (rc)
1224df04ced6SEddie James 		dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc);
1225df04ced6SEddie James 
1226df04ced6SEddie James 	return rc;
12275b5513b8SEddie James }
12285679ed99SJean Delvare EXPORT_SYMBOL_GPL(occ_setup);
12295679ed99SJean Delvare 
1230cf8e9f21SEddie James void occ_shutdown(struct occ *occ)
1231cf8e9f21SEddie James {
1232cf8e9f21SEddie James 	occ_shutdown_sysfs(occ);
1233cf8e9f21SEddie James 
1234cf8e9f21SEddie James 	if (occ->hwmon)
1235cf8e9f21SEddie James 		hwmon_device_unregister(occ->hwmon);
1236cf8e9f21SEddie James }
1237cf8e9f21SEddie James EXPORT_SYMBOL_GPL(occ_shutdown);
1238cf8e9f21SEddie James 
12395679ed99SJean Delvare MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
12405679ed99SJean Delvare MODULE_DESCRIPTION("Common OCC hwmon code");
12415679ed99SJean Delvare MODULE_LICENSE("GPL");
1242