Skip to contentSkip to conversation

hwmon and fsi: Add On-Chip Controller Driver

PATCH In review Eddie James 3 participants 13 messages Nov 8, 2018 – Nov 12, 2018

Subsystems (5)
lore
Share & tools
lore

Eddie James

author maintainer
#0 lore raw

v6 cover hwmon and fsi: Add On-Chip Controller Driver

From: Eddie James <[email protected]>

This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management.

The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already available as an FSI client driver.

For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus.

Changes since v5:
* Makefile fix when compiling both P8 and P9 versions
* Spelling fix in hwmon doc
* Added an additional sentence for P9 binding doc to explain that OCC isn't an FSI slave device.

Changes since v4:
* Make the hwmon attributes conform almost completely to standard names and values. The only exception is powerX_cap_user and powerX_cap_user_source.
* Improve hwmon documentation.
* Add ibm,p9-occ dt documentation.

Changes since v3:
* Add the FSI OCC driver.
* Pull the sysfs attribute code into it's own file for cleanliness.
* Various fixes for attribute creation and integer overflow.

Changes since v2:
* Add sysfs_notify for the error and throttling attributes when change is detected.
* Removed occs_present counting of devices bound.
* Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound.
* Added default cases (return EINVAL) for all sensor show functions.
* Added temperature fault sensor.
* Added back dt binding documentation for P9 to address checkpatch warning.
* Added occs_present attribute from the poll response.

Changes since v1:
* Remove wait loop in P9 code, as that is now handled by FSI OCC driver.
* Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically.
* Moved OCC response code definitions to the OCC include file.
* Fixed includes.
* Changed some structure fields to __beXX as that is what they are.
* Changed some errnos.
* Removed some dev_err().
* Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop.
* Refactored error handling a bit.

Eddie James (10):
dt-bindings: fsi: Add P9 OCC device documentation
fsi: Add On-Chip Controller (OCC) driver
Documentation: hwmon: Add OCC documentation
dt-bindings: i2c: Add P8 OCC hwmon device documentation
hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response
hwmon (occ): Add sensor types and versions
hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data

 .../devicetree/bindings/fsi/ibm,p9-occ.txt         |   16 +
 .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt   |   25 +
 Documentation/hwmon/occ                            |  112 ++
 drivers/fsi/Kconfig                                |   10 +
 drivers/fsi/Makefile                               |    1 +
 drivers/fsi/fsi-occ.c                              |  599 +++++++++++
 drivers/hwmon/Kconfig                              |    2 +
 drivers/hwmon/Makefile                             |    1 +
 drivers/hwmon/occ/Kconfig                          |   31 +
 drivers/hwmon/occ/Makefile                         |    5 +
 drivers/hwmon/occ/common.c                         | 1098 ++++++++++++++++++++
 drivers/hwmon/occ/common.h                         |  128 +++
 drivers/hwmon/occ/p8_i2c.c                         |  255 +++++
 drivers/hwmon/occ/p9_sbe.c                         |  106 ++
 drivers/hwmon/occ/sysfs.c                          |  188 ++++
 include/linux/fsi-occ.h                            |   25 +
 16 files changed, 2602 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt
 create mode 100644 Documentation/hwmon/occ
 create mode 100644 drivers/fsi/fsi-occ.c
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/common.c
 create mode 100644 drivers/hwmon/occ/common.h
 create mode 100644 drivers/hwmon/occ/p8_i2c.c
 create mode 100644 drivers/hwmon/occ/p9_sbe.c
 create mode 100644 drivers/hwmon/occ/sysfs.c
 create mode 100644 include/linux/fsi-occ.h
Signature · 1 line
1.8.3.1
author maintainer
#1 lore raw

PATCH v6 1/10 dt-bindings: fsi: Add P9 OCC device documentation

From: Eddie James <[email protected]>

Document the bindings for the FSI-attached POWER9 On-Chip Controller.

---
 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt
Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt new file +16 -0
new file mode 100644
index 0000000..99ca986
@@ -0,0 +1,16 @@
+Device-tree bindings for FSI-attached POWER9 On-Chip Controller (OCC)
+---------------------------------------------------------------------
+
+This is the binding for the P9 On-Chip Controller accessed over FSI from a
+service processor. See fsi.txt for details on bindings for FSI slave and CFAM
+nodes. The OCC is not an FSI slave device itself, rather it is accessed
+through the SBE fifo.
+
+Required properties:
+ - compatible = "ibm,p9-occ"
+
+Examples:
+
+    occ {
+        compatible = "ibm,p9-occ";
+    };
Signature · 1 line
1.8.3.1
author maintainer
#2 lore raw

PATCH v6 2/10 fsi: Add On-Chip Controller (OCC) driver

In reply to Eddie James

From: Eddie James <[email protected]>

The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system.

This driver provides an atomic communications channel between a service processor (e.g. a BMC) and the OCC. The driver is dependent on the FSI SBEFIFO driver to get hardware access through the SBE to the OCC SRAM. Commands are issued to the SBE to send or fetch data to the SRAM.

---
 drivers/fsi/Kconfig     |  10 +
 drivers/fsi/Makefile    |   1 +
 drivers/fsi/fsi-occ.c   | 599 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/fsi-occ.h |  25 ++
 4 files changed, 635 insertions(+)
 create mode 100644 drivers/fsi/fsi-occ.c
 create mode 100644 include/linux/fsi-occ.h
drivers/fsi/Kconfig +10 -0
index af3a20d..ea2f4a1 100644
@@ -64,4 +64,14 @@ config FSI_SBEFIFO
 	a pipe-like FSI device for communicating with the self boot engine
 	(SBE) on POWER processors.
 
+config FSI_OCC
+	tristate "OCC SBEFIFO client device driver"
+	depends on FSI_SBEFIFO
+	---help---
+	This option enables an SBEFIFO based On-Chip Controller (OCC) device
+	driver. The OCC is a device embedded on a POWER processor that collects
+	and aggregates sensor data from the processor and system. The OCC can
+	provide the raw sensor data as well as perform thermal and power
+	management on the system.
+
 endif
drivers/fsi/Makefile +1 -0
index a50d6ce..62687ec 100644
@@ -5,3 +5,4 @@ obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
 obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o
 obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
 obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
+obj-$(CONFIG_FSI_OCC) += fsi-occ.o
drivers/fsi/fsi-occ.c new file +599 -0

599 lines not on this page · raw message

include/linux/fsi-occ.h new file +25 -0
new file mode 100644
index 0000000..d4cdc2a
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef LINUX_FSI_OCC_H
+#define LINUX_FSI_OCC_H
+
+struct device;
+
+#define OCC_RESP_CMD_IN_PRG		0xFF
+#define OCC_RESP_SUCCESS		0
+#define OCC_RESP_CMD_INVAL		0x11
+#define OCC_RESP_CMD_LEN_INVAL		0x12
+#define OCC_RESP_DATA_INVAL		0x13
+#define OCC_RESP_CHKSUM_ERR		0x14
+#define OCC_RESP_INT_ERR		0x15
+#define OCC_RESP_BAD_STATE		0x16
+#define OCC_RESP_CRIT_EXCEPT		0xE0
+#define OCC_RESP_CRIT_INIT		0xE1
+#define OCC_RESP_CRIT_WATCHDOG		0xE2
+#define OCC_RESP_CRIT_OCB		0xE3
+#define OCC_RESP_CRIT_HW		0xE4
+
+int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
+		   void *response, size_t *resp_len);
+
+#endif /* LINUX_FSI_OCC_H */
Signature · 1 line
1.8.3.1
author maintainer
#3 lore raw

PATCH v6 3/10 Documentation: hwmon: Add OCC documentation

In reply to Eddie James

From: Eddie James <[email protected]>

Document the hwmon interface for the OCC.

---
 Documentation/hwmon/occ | 112 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 Documentation/hwmon/occ
Documentation/hwmon/occ new file +112 -0
new file mode 100644
index 0000000..e787596
@@ -0,0 +1,112 @@
+Kernel driver occ-hwmon
+=======================
+
+Supported chips:
+  * POWER8
+  * POWER9
+
+Author: Eddie James <[email protected]>
+
+Description
+-----------
+
+This driver supports hardware monitoring for the On-Chip Controller (OCC)
+embedded on POWER processors. The OCC is a device that collects and aggregates
+sensor data from the processor and the system. The OCC can provide the raw
+sensor data as well as perform thermal and power management on the system.
+
+The P8 version of this driver is a client driver of I2C. It may be probed
+manually if an "ibm,p8-occ-hwmon" compatible device is found under the
+appropriate I2C bus node in the device-tree.
+
+The P9 version of this driver is a client driver of the FSI-based OCC driver.
+It will be probed automatically by the FSI-based OCC driver.
+
+Sysfs entries
+-------------
+
+The following attributes are supported. All attributes are read-only unless
+specified.
+
+The OCC sensor ID is an integer that represents the unique identifier of the
+sensor with respect to the OCC. For example, a temperature sensor for the third
+DIMM slot in the system may have a sensor ID of 7. This mapping is unavailable
+to the device driver, which must therefore export the sensor ID as-is.
+
+Some entries are only present with certain OCC sensor versions or only on
+certain OCCs in the system. The version number is not exported to the user
+but can be inferred.
+
+temp[1-n]_label		OCC sensor ID.
+[with temperature sensor version 1]
+    temp[1-n]_input	Measured temperature of the component in millidegrees
+			Celsius.
+[with temperature sensor version >= 2]
+    temp[1-n]_type		The FRU (Field Replaceable Unit) type
+				(represented by an integer) for the component
+				that this sensor measures.
+    temp[1-n]_fault		Temperature sensor fault boolean; 1 to indicate
+				that a fault is present or 0 to indicate that
+				no fault is present.
+    [with type == 3 (FRU type is VRM)]
+        temp[1-n]_alarm		VRM temperature alarm boolean; 1 to indicate
+				alarm, 0 to indicate no alarm
+    [else]
+        temp[1-n]_input		Measured temperature of the component in
+				millidegrees Celsius.
+
+freq[1-n]_label		OCC sensor ID.
+freq[1-n]_input		Measured frequency of the component in MHz.
+
+power[1-n]_input	Latest measured power reading of the component in
+			microwatts.
+power[1-n]_average	Average power of the component in microwatts.
+power[1-n]_average_interval	The amount of time over which the power average
+				was taken in microseconds.
+[with power sensor version < 2]
+    power[1-n]_label	OCC sensor ID.
+[with power sensor version >= 2]
+    power[1-n]_label	OCC sensor ID + function ID + channel in the form
+			of a string, delimited by underscores, i.e. "0_15_1".
+			Both the function ID and channel are integers that
+			further identify the power sensor.
+[with power sensor version 0xa0]
+    power[1-n]_label	OCC sensor ID + sensor type in the form of a string,
+			delimited by an underscore, i.e. "0_system". Sensor
+			type will be one of "system", "proc", "vdd" or "vdn".
+			For this sensor version, OCC sensor ID will be the same
+			for all power sensors.
+[present only on "master" OCC; represents the whole system power; only one of
+ this type of power sensor will be present]
+    power[1-n]_label		"system"
+    power[1-n]_input		Latest system output power in microwatts.
+    power[1-n]_cap		Current system power cap in microwatts.
+    power[1-n]_cap_not_redundant	System power cap in microwatts when
+					there is not redundant power.
+    power[1-n]_cap_max		Maximum power cap that the OCC can enforce in
+				microwatts.
+    power[1-n]_cap_min		Minimum power cap that the OCC can enforce in
+				microwatts.
+    power[1-n]_cap_user		The power cap set by the user, in microwatts.
+				This attribute will return 0 if no user power
+				cap has been set. This attribute is read-write,
+				but writing any precision below watts will be
+				ignored, i.e. requesting a power cap of
+				500900000 microwatts will result in a power cap
+				request of 500 watts.
+    [with caps sensor version > 1]
+        power[1-n]_cap_user_source	Indicates how the user power cap was
+					set. This is an integer that maps to
+					system or firmware components that can
+					set the user power cap.
+
+The following "extn" sensors are exported as a way for the OCC to provide data
+that doesn't fit anywhere else. The meaning of these sensors is entirely
+dependent on their data, and cannot be statically defined.
+
+extn[1-n]_label		ASCII ID or OCC sensor ID.
+extn[1-n]_flags		This is one byte hexadecimal value. Bit 7 indicates the
+			type of the label attribute; 1 for sensor ID, 0 for
+			ASCII ID. Other bits are reserved.
+extn[1-n]_input		6 bytes of hexadecimal data, with a meaning defined by
+			the sensor ID.
Signature · 1 line
1.8.3.1
author maintainer
#4 lore raw

PATCH v6 4/10 dt-bindings: i2c: Add P8 OCC hwmon device documentation

In reply to Eddie James

From: Eddie James <[email protected]>

Document the bindings for I2C-based OCC hwmon device.

---
 .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt   | 25 ++++++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt
Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt new file +25 -0
new file mode 100644
index 0000000..5dc5d2e
@@ -0,0 +1,25 @@
+Device-tree bindings for I2C-based On-Chip Controller hwmon device
+------------------------------------------------------------------
+
+Required properties:
+ - compatible = "ibm,p8-occ-hwmon";
+ - reg = <I2C address>;			: I2C bus address
+
+Examples:
+
+    i2c-bus@100 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        clock-frequency = <100000>;
+        < more properties >
+
+        occ-hwmon@1 {
+            compatible = "ibm,p8-occ-hwmon";
+            reg = <0x50>;
+        };
+
+        occ-hwmon@2 {
+            compatible = "ibm,p8-occ-hwmon";
+            reg = <0x51>;
+        };
+    };
Signature · 1 line
1.8.3.1
author maintainer
#5 lore raw

PATCH v6 5/10 hwmon: Add On-Chip Controller (OCC) hwmon driver

In reply to Eddie James

From: Eddie James <[email protected]>

The OCC is a device embedded on a POWER processor that collects and aggregates sensor data from the processor and system. The OCC can provide the raw sensor data as well as perform thermal and power management on the system.

This driver provides a hwmon interface to the OCC from a service processor (e.g. a BMC). The driver supports both POWER8 and POWER9 OCCs. Communications with the POWER8 OCC are established over standard I2C bus. The driver communicates with the POWER9 OCC through the FSI-based OCC driver, which handles the lower-level communication details.

This patch lays out the structure of the OCC hwmon driver. There are two platform drivers, one each for P8 and P9 OCCs. These are probed through the I2C tree and the FSI-based OCC driver, respectively. The patch also defines the first common structures and methods between the two OCC versions.

---
 drivers/hwmon/Kconfig      |  2 ++
 drivers/hwmon/Makefile     |  1 +
 drivers/hwmon/occ/Kconfig  | 31 +++++++++++++++++++++
 drivers/hwmon/occ/Makefile |  5 ++++
 drivers/hwmon/occ/common.c | 40 +++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h | 34 +++++++++++++++++++++++
 drivers/hwmon/occ/p8_i2c.c | 61 +++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/p9_sbe.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 242 insertions(+)
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/common.c
 create mode 100644 drivers/hwmon/occ/common.h
 create mode 100644 drivers/hwmon/occ/p8_i2c.c
 create mode 100644 drivers/hwmon/occ/p9_sbe.c
drivers/hwmon/Kconfig +2 -0
index 81da17a..532a053 100644
@@ -1293,6 +1293,8 @@ config SENSORS_NSA320
 	  This driver can also be built as a module. If so, the module
 	  will be called nsa320-hwmon.
 
+source drivers/hwmon/occ/Kconfig
+
 config SENSORS_PCF8591
 	tristate "Philips PCF8591 ADC/DAC"
 	depends on I2C
drivers/hwmon/Makefile +1 -0
index 93f7f41..f5c7b44 100644
@@ -178,6 +178,7 @@ obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
 obj-$(CONFIG_SENSORS_XGENE)	+= xgene-hwmon.o
 
+obj-$(CONFIG_SENSORS_OCC)	+= occ/
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
 ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
drivers/hwmon/occ/Kconfig new file +31 -0
new file mode 100644
index 0000000..6668662
@@ -0,0 +1,31 @@
+#
+# On-Chip Controller configuration
+#
+
+config SENSORS_OCC_P8_I2C
+	tristate "POWER8 OCC through I2C"
+	depends on I2C
+	select SENSORS_OCC
+	help
+	 This option enables support for monitoring sensors provided by the
+	 On-Chip Controller (OCC) on a POWER8 processor. Communications with
+	 the OCC are established through I2C bus.
+
+	 This driver can also be built as a module. If so, the module will be
+	 called occ-p8-hwmon.
+
+config SENSORS_OCC_P9_SBE
+	tristate "POWER9 OCC through SBE"
+	depends on FSI_OCC
+	select SENSORS_OCC
+	help
+	 This option enables support for monitoring sensors provided by the
+	 On-Chip Controller (OCC) on a POWER9 processor. Communications with
+	 the OCC are established through SBE fifo on an FSI bus.
+
+	 This driver can also be built as a module. If so, the module will be
+	 called occ-p9-hwmon.
+
+config SENSORS_OCC
+	bool "POWER On-Chip Controller"
+	depends on SENSORS_OCC_P8_I2C || SENSORS_OCC_P9_SBE
drivers/hwmon/occ/Makefile new file +5 -0
new file mode 100644
index 0000000..57c0e91
@@ -0,0 +1,5 @@
+occ-p8-hwmon-objs := common.o p8_i2c.o
+occ-p9-hwmon-objs := common.o p9_sbe.o
+
+obj-$(CONFIG_SENSORS_OCC_P8_I2C) += occ-p8-hwmon.o
+obj-$(CONFIG_SENSORS_OCC_P9_SBE) += occ-p9-hwmon.o
drivers/hwmon/occ/common.c new file +40 -0
new file mode 100644
index 0000000..81acd4b
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/device.h>
+
+#include "common.h"
+
+static int occ_poll(struct occ *occ)
+{
+	u16 checksum = occ->poll_cmd_data + 1;
+	u8 cmd[8];
+
+	/* big endian */
+	cmd[0] = 0;			/* sequence number */
+	cmd[1] = 0;			/* cmd type */
+	cmd[2] = 0;			/* data length msb */
+	cmd[3] = 1;			/* data length lsb */
+	cmd[4] = occ->poll_cmd_data;	/* data */
+	cmd[5] = checksum >> 8;		/* checksum msb */
+	cmd[6] = checksum & 0xFF;	/* checksum lsb */
+	cmd[7] = 0;
+
+	return occ->send_cmd(occ, cmd);
+}
+
+int occ_setup(struct occ *occ, const char *name)
+{
+	int rc;
+
+	rc = occ_poll(occ);
+	if (rc == -ESHUTDOWN) {
+		dev_info(occ->bus_dev, "host is not ready\n");
+		return rc;
+	} else if (rc < 0) {
+		dev_err(occ->bus_dev, "failed to get OCC poll response: %d\n",
+			rc);
+		return rc;
+	}
+
+	return 0;
+}
drivers/hwmon/occ/common.h new file +34 -0
new file mode 100644
index 0000000..b44fee2
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.
+
+#ifndef OCC_COMMON_H
+#define OCC_COMMON_H
+
+struct device;
+
+#define OCC_RESP_DATA_BYTES		4089
+
+/*
+ * Same response format for all OCC versions.
+ * Allocate the largest possible response.
+ */
+struct occ_response {
+	u8 seq_no;
+	u8 cmd_type;
+	u8 return_status;
+	__be16 data_length;
+	u8 data[OCC_RESP_DATA_BYTES];
+	__be16 checksum;
+} __packed;
+
+struct occ {
+	struct device *bus_dev;
+
+	struct occ_response resp;
+
+	u8 poll_cmd_data;		/* to perform OCC poll command */
+	int (*send_cmd)(struct occ *occ, u8 *cmd);
+};
+
+int occ_setup(struct occ *occ, const char *name);
+
+#endif /* OCC_COMMON_H */
drivers/hwmon/occ/p8_i2c.c new file +61 -0

61 lines not on this page · raw message

drivers/hwmon/occ/p9_sbe.c new file +68 -0

68 lines not on this page · raw message

Signature · 1 line
1.8.3.1
author maintainer
#6 lore raw

PATCH v6 6/10 hwmon (occ): Add command transport method for P8 and P9

In reply to Eddie James

From: Eddie James <[email protected]>

For the P8 OCC, add the procedure to send a command to the OCC over I2C bus. This involves writing the OCC command registers with serial communication operations (SCOMs) interpreted by the I2C slave. For the P9 OCC, add a procedure to use the OCC in-kernel API to send a command to the OCC through the SBE.

---
 drivers/hwmon/occ/p8_i2c.c | 185 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/hwmon/occ/p9_sbe.c |  38 +++++++++-
 2 files changed, 221 insertions(+), 2 deletions(-)
drivers/hwmon/occ/p8_i2c.c +184 -1
index 4f3937d..e3326ff 100644
@@ -2,11 +2,29 @@
 
 #include <linux/device.h>
 #include <linux/errno.h>
+#include <linux/fsi-occ.h>
 #include <linux/i2c.h>
+#include <linux/jiffies.h>
 #include <linux/module.h>
+#include <linux/sched.h>
+#include <asm/unaligned.h>
 
 #include "common.h"
 
+#define OCC_TIMEOUT_MS			1000
+#define OCC_CMD_IN_PRG_WAIT_MS		50
+
+/* OCB (on-chip control bridge - interface to OCC) registers */
+#define OCB_DATA1			0x6B035
+#define OCB_ADDR			0x6B070
+#define OCB_DATA3			0x6B075
+
+/* OCC SRAM address space */
+#define OCC_SRAM_ADDR_CMD		0xFFFF6000
+#define OCC_SRAM_ADDR_RESP		0xFFFF7000
+
+#define OCC_DATA_ATTN			0x20010000
+
 struct p8_i2c_occ {
 	struct occ occ;
 	struct i2c_client *client;
@@ -14,9 +32,174 @@ struct p8_i2c_occ {
 
 #define to_p8_i2c_occ(x)	container_of((x), struct p8_i2c_occ, occ)
 
+static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data)
+{
+	ssize_t rc;
+	__be64 buf;
+	struct i2c_msg msgs[2];
+
+	/* p8 i2c slave requires shift */
+	address <<= 1;
+
+	msgs[0].addr = client->addr;
+	msgs[0].flags = client->flags & I2C_M_TEN;
+	msgs[0].len = sizeof(u32);
+	/* address is a scom address; bus-endian */
+	msgs[0].buf = (char *)&address;
+
+	/* data from OCC is big-endian */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD;
+	msgs[1].len = sizeof(u64);
+	msgs[1].buf = (char *)&buf;
+
+	rc = i2c_transfer(client->adapter, msgs, 2);
+	if (rc < 0)
+		return rc;
+
+	*(u64 *)data = be64_to_cpu(buf);
+
+	return 0;
+}
+
+static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data)
+{
+	u32 buf[3];
+	ssize_t rc;
+
+	/* p8 i2c slave requires shift */
+	address <<= 1;
+
+	/* address is bus-endian; data passed through from user as-is */
+	buf[0] = address;
+	memcpy(&buf[1], &data[4], sizeof(u32));
+	memcpy(&buf[2], data, sizeof(u32));
+
+	rc = i2c_master_send(client, (const char *)buf, sizeof(buf));
+	if (rc < 0)
+		return rc;
+	else if (rc != sizeof(buf))
+		return -EIO;
+
+	return 0;
+}
+
+static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address,
+				  u32 data0, u32 data1)
+{
+	u8 buf[8];
+
+	memcpy(buf, &data0, 4);
+	memcpy(buf + 4, &data1, 4);
+
+	return p8_i2c_occ_putscom(client, address, buf);
+}
+
+static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address,
+				 u8 *data)
+{
+	__be32 data0, data1;
+
+	memcpy(&data0, data, 4);
+	memcpy(&data1, data + 4, 4);
+
+	return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0),
+				      be32_to_cpu(data1));
+}
+
 static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd)
 {
-	return -EOPNOTSUPP;
+	int i, rc;
+	unsigned long start;
+	u16 data_length;
+	const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
+	const long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
+	struct p8_i2c_occ *ctx = to_p8_i2c_occ(occ);
+	struct i2c_client *client = ctx->client;
+	struct occ_response *resp = &occ->resp;
+
+	start = jiffies;
+
+	/* set sram address for command */
+	rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0);
+	if (rc)
+		return rc;
+
+	/* write command (expected to already be BE), we need bus-endian... */
+	rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd);
+	if (rc)
+		return rc;
+
+	/* trigger OCC attention */
+	rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0);
+	if (rc)
+		return rc;
+
+	do {
+		/* set sram address for response */
+		rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR,
+					    OCC_SRAM_ADDR_RESP, 0);
+		if (rc)
+			return rc;
+
+		rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)resp);
+		if (rc)
+			return rc;
+
+		/* wait for OCC */
+		if (resp->return_status == OCC_RESP_CMD_IN_PRG) {
+			rc = -EALREADY;
+
+			if (time_after(jiffies, start + timeout))
+				break;
+
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(wait_time);
+		}
+	} while (rc);
+
+	/* check the OCC response */
+	switch (resp->return_status) {
+	case OCC_RESP_CMD_IN_PRG:
+		rc = -ETIMEDOUT;
+		break;
+	case OCC_RESP_SUCCESS:
+		rc = 0;
+		break;
+	case OCC_RESP_CMD_INVAL:
+	case OCC_RESP_CMD_LEN_INVAL:
+	case OCC_RESP_DATA_INVAL:
+	case OCC_RESP_CHKSUM_ERR:
+		rc = -EINVAL;
+		break;
+	case OCC_RESP_INT_ERR:
+	case OCC_RESP_BAD_STATE:
+	case OCC_RESP_CRIT_EXCEPT:
+	case OCC_RESP_CRIT_INIT:
+	case OCC_RESP_CRIT_WATCHDOG:
+	case OCC_RESP_CRIT_OCB:
+	case OCC_RESP_CRIT_HW:
+		rc = -EREMOTEIO;
+		break;
+	default:
+		rc = -EPROTO;
+	}
+
+	if (rc < 0)
+		return rc;
+
+	data_length = get_unaligned_be16(&resp->data_length);
+	if (data_length > OCC_RESP_DATA_BYTES)
+		return -EMSGSIZE;
+
+	/* fetch the rest of the response data */
+	for (i = 8; i < data_length + 7; i += 8) {
+		rc = p8_i2c_occ_getscom(client, OCB_DATA3, ((u8 *)resp) + i);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
 }
 
 static int p8_i2c_occ_probe(struct i2c_client *client,
drivers/hwmon/occ/p9_sbe.c +37 -1
index c2fa34e..0ed5e22 100644
@@ -2,6 +2,7 @@
 
 #include <linux/device.h>
 #include <linux/errno.h>
+#include <linux/fsi-occ.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 
@@ -16,7 +17,42 @@ struct p9_sbe_occ {
 
 static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd)
 {
-	return -EOPNOTSUPP;
+	struct occ_response *resp = &occ->resp;
+	struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
+	size_t resp_len = sizeof(*resp);
+	int rc;
+
+	rc = fsi_occ_submit(ctx->sbe, cmd, 8, resp, &resp_len);
+	if (rc < 0)
+		return rc;
+
+	switch (resp->return_status) {
+	case OCC_RESP_CMD_IN_PRG:
+		rc = -ETIMEDOUT;
+		break;
+	case OCC_RESP_SUCCESS:
+		rc = 0;
+		break;
+	case OCC_RESP_CMD_INVAL:
+	case OCC_RESP_CMD_LEN_INVAL:
+	case OCC_RESP_DATA_INVAL:
+	case OCC_RESP_CHKSUM_ERR:
+		rc = -EINVAL;
+		break;
+	case OCC_RESP_INT_ERR:
+	case OCC_RESP_BAD_STATE:
+	case OCC_RESP_CRIT_EXCEPT:
+	case OCC_RESP_CRIT_INIT:
+	case OCC_RESP_CRIT_WATCHDOG:
+	case OCC_RESP_CRIT_OCB:
+	case OCC_RESP_CRIT_HW:
+		rc = -EREMOTEIO;
+		break;
+	default:
+		rc = -EPROTO;
+	}
+
+	return rc;
 }
 
 static int p9_sbe_occ_probe(struct platform_device *pdev)
Signature · 1 line
1.8.3.1
author maintainer
#7 lore raw

PATCH v6 7/10 hwmon (occ): Parse OCC poll response

In reply to Eddie James

From: Eddie James <[email protected]>

Add method to parse the response from the OCC poll command. This only needs to be done during probe(), since the OCC shouldn't change the number or format of sensors while it's running. The parsed response allows quick access to sensor data, as well as information on the number and version of sensors, which we need to instantiate hwmon attributes.

---
 drivers/hwmon/occ/common.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h | 55 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 116 insertions(+)
drivers/hwmon/occ/common.c +61 -0
index 81acd4b..a066509 100644
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/device.h>
+#include <linux/kernel.h>
 
 #include "common.h"
 
@@ -22,6 +23,64 @@ static int occ_poll(struct occ *occ)
 	return occ->send_cmd(occ, cmd);
 }
 
+/* only need to do this once at startup, as OCC won't change sensors on us */
+static void occ_parse_poll_response(struct occ *occ)
+{
+	unsigned int i, old_offset, offset = 0, size = 0;
+	struct occ_sensor *sensor;
+	struct occ_sensors *sensors = &occ->sensors;
+	struct occ_response *resp = &occ->resp;
+	struct occ_poll_response *poll =
+		(struct occ_poll_response *)&resp->data[0];
+	struct occ_poll_response_header *header = &poll->header;
+	struct occ_sensor_data_block *block = &poll->block;
+
+	dev_info(occ->bus_dev, "OCC found, code level: %.16s\n",
+		 header->occ_code_level);
+
+	for (i = 0; i < header->num_sensor_data_blocks; ++i) {
+		block = (struct occ_sensor_data_block *)((u8 *)block + offset);
+		old_offset = offset;
+		offset = (block->header.num_sensors *
+			  block->header.sensor_length) + sizeof(block->header);
+		size += offset;
+
+		/* validate all the length/size fields */
+		if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) {
+			dev_warn(occ->bus_dev, "exceeded response buffer\n");
+			return;
+		}
+
+		dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n",
+			old_offset, offset - 1, block->header.eye_catcher,
+			block->header.num_sensors);
+
+		/* match sensor block type */
+		if (strncmp(block->header.eye_catcher, "TEMP", 4) == 0)
+			sensor = &sensors->temp;
+		else if (strncmp(block->header.eye_catcher, "FREQ", 4) == 0)
+			sensor = &sensors->freq;
+		else if (strncmp(block->header.eye_catcher, "POWR", 4) == 0)
+			sensor = &sensors->power;
+		else if (strncmp(block->header.eye_catcher, "CAPS", 4) == 0)
+			sensor = &sensors->caps;
+		else if (strncmp(block->header.eye_catcher, "EXTN", 4) == 0)
+			sensor = &sensors->extended;
+		else {
+			dev_warn(occ->bus_dev, "sensor not supported %.4s\n",
+				 block->header.eye_catcher);
+			continue;
+		}
+
+		sensor->num_sensors = block->header.num_sensors;
+		sensor->version = block->header.sensor_format;
+		sensor->data = &block->data;
+	}
+
+	dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", size,
+		sizeof(*header), size + sizeof(*header));
+}
+
 int occ_setup(struct occ *occ, const char *name)
 {
 	int rc;
@@ -36,5 +95,7 @@ int occ_setup(struct occ *occ, const char *name)
 		return rc;
 	}
 
+	occ_parse_poll_response(occ);
+
 	return 0;
 }
drivers/hwmon/occ/common.h +55 -0

65 lines not on this page · raw message

Signature · 1 line
1.8.3.1
author maintainer
#8 lore raw

PATCH v6 8/10 hwmon (occ): Add sensor types and versions

In reply to Eddie James

From: Eddie James <[email protected]>

Add structures to define all sensor types and versions. Add sysfs show and store functions for each sensor type. Add a method to construct the "set user power cap" command and send it to the OCC. Add rate limit to polling the OCC (in case user-space reads our hwmon entries rapidly).

---
 drivers/hwmon/occ/common.c | 621 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h |   6 +
 drivers/hwmon/occ/p8_i2c.c |   1 +
 drivers/hwmon/occ/p9_sbe.c |   1 +
 4 files changed, 629 insertions(+)
drivers/hwmon/occ/common.c +621 -0
index a066509..f7220132 100644
@@ -1,10 +1,116 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/device.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
 #include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <asm/unaligned.h>
 
 #include "common.h"
 
+#define EXTN_FLAG_SENSOR_ID		BIT(7)
+
+#define OCC_UPDATE_FREQUENCY		msecs_to_jiffies(1000)
+
+#define OCC_TEMP_SENSOR_FAULT		0xFF
+
+#define OCC_FRU_TYPE_VRM		3
+
+/* OCC sensor type and version definitions */
+
+struct temp_sensor_1 {
+	u16 sensor_id;
+	u16 value;
+} __packed;
+
+struct temp_sensor_2 {
+	u32 sensor_id;
+	u8 fru_type;
+	u8 value;
+} __packed;
+
+struct freq_sensor_1 {
+	u16 sensor_id;
+	u16 value;
+} __packed;
+
+struct freq_sensor_2 {
+	u32 sensor_id;
+	u16 value;
+} __packed;
+
+struct power_sensor_1 {
+	u16 sensor_id;
+	u32 update_tag;
+	u32 accumulator;
+	u16 value;
+} __packed;
+
+struct power_sensor_2 {
+	u32 sensor_id;
+	u8 function_id;
+	u8 apss_channel;
+	u16 reserved;
+	u32 update_tag;
+	u64 accumulator;
+	u16 value;
+} __packed;
+
+struct power_sensor_data {
+	u16 value;
+	u32 update_tag;
+	u64 accumulator;
+} __packed;
+
+struct power_sensor_data_and_time {
+	u16 update_time;
+	u16 value;
+	u32 update_tag;
+	u64 accumulator;
+} __packed;
+
+struct power_sensor_a0 {
+	u32 sensor_id;
+	struct power_sensor_data_and_time system;
+	u32 reserved;
+	struct power_sensor_data_and_time proc;
+	struct power_sensor_data vdd;
+	struct power_sensor_data vdn;
+} __packed;
+
+struct caps_sensor_2 {
+	u16 cap;
+	u16 system_power;
+	u16 n_cap;
+	u16 max;
+	u16 min;
+	u16 user;
+	u8 user_source;
+} __packed;
+
+struct caps_sensor_3 {
+	u16 cap;
+	u16 system_power;
+	u16 n_cap;
+	u16 max;
+	u16 hard_min;
+	u16 soft_min;
+	u16 user;
+	u8 user_source;
+} __packed;
+
+struct extended_sensor {
+	union {
+		u8 name[4];
+		u32 sensor_id;
+	};
+	u8 flags;
+	u8 reserved;
+	u8 data[6];
+} __packed;
+
 static int occ_poll(struct occ *occ)
 {
 	u16 checksum = occ->poll_cmd_data + 1;
@@ -20,9 +126,521 @@ static int occ_poll(struct occ *occ)
 	cmd[6] = checksum & 0xFF;	/* checksum lsb */
 	cmd[7] = 0;
 
+	/* mutex should already be locked if necessary */
 	return occ->send_cmd(occ, cmd);
 }
 
+static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
+{
+	int rc;
+	u8 cmd[8];
+	u16 checksum = 0x24;
+	__be16 user_power_cap_be = cpu_to_be16(user_power_cap);
+
+	cmd[0] = 0;
+	cmd[1] = 0x22;
+	cmd[2] = 0;
+	cmd[3] = 2;
+
+	memcpy(&cmd[4], &user_power_cap_be, 2);
+
+	checksum += cmd[4] + cmd[5];
+	cmd[6] = checksum >> 8;
+	cmd[7] = checksum & 0xFF;
+
+	rc = mutex_lock_interruptible(&occ->lock);
+	if (rc)
+		return rc;
+
+	rc = occ->send_cmd(occ, cmd);
+
+	mutex_unlock(&occ->lock);
+
+	return rc;
+}
+
+static int occ_update_response(struct occ *occ)
+{
+	int rc = mutex_lock_interruptible(&occ->lock);
+
+	if (rc)
+		return rc;
+
+	/* limit the maximum rate of polling the OCC */
+	if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) {
+		rc = occ_poll(occ);
+		occ->last_update = jiffies;
+	}
+
+	mutex_unlock(&occ->lock);
+	return rc;
+}
+
+static ssize_t occ_show_temp_1(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u32 val = 0;
+	struct temp_sensor_1 *temp;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	temp = ((struct temp_sensor_1 *)sensors->temp.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = get_unaligned_be16(&temp->sensor_id);
+		break;
+	case 1:
+		val = get_unaligned_be16(&temp->value) * 1000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t occ_show_temp_2(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u32 val = 0;
+	struct temp_sensor_2 *temp;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	temp = ((struct temp_sensor_2 *)sensors->temp.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = get_unaligned_be32(&temp->sensor_id);
+		break;
+	case 1:
+		val = temp->value;
+		if (val == OCC_TEMP_SENSOR_FAULT)
+			return -EREMOTEIO;
+
+		/*
+		 * VRM doesn't return temperature, only alarm bit. This
+		 * attribute maps to tempX_alarm instead of tempX_input for
+		 * VRM
+		 */
+		if (temp->fru_type != OCC_FRU_TYPE_VRM) {
+			/* sensor not ready */
+			if (val == 0)
+				return -EAGAIN;
+
+			val *= 1000;
+		}
+		break;
+	case 2:
+		val = temp->fru_type;
+		break;
+	case 3:
+		val = temp->value == OCC_TEMP_SENSOR_FAULT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t occ_show_freq_1(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u16 val = 0;
+	struct freq_sensor_1 *freq;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	freq = ((struct freq_sensor_1 *)sensors->freq.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = get_unaligned_be16(&freq->sensor_id);
+		break;
+	case 1:
+		val = get_unaligned_be16(&freq->value);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t occ_show_freq_2(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u32 val = 0;
+	struct freq_sensor_2 *freq;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	freq = ((struct freq_sensor_2 *)sensors->freq.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = get_unaligned_be32(&freq->sensor_id);
+		break;
+	case 1:
+		val = get_unaligned_be16(&freq->value);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t occ_show_power_1(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u64 val = 0;
+	struct power_sensor_1 *power;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	power = ((struct power_sensor_1 *)sensors->power.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = get_unaligned_be16(&power->sensor_id);
+		break;
+	case 1:
+		val = get_unaligned_be32(&power->accumulator) /
+			get_unaligned_be32(&power->update_tag);
+		val *= 1000000ULL;
+		break;
+	case 2:
+		val = get_unaligned_be32(&power->update_tag) *
+			occ->powr_sample_time_us;
+		break;
+	case 3:
+		val = get_unaligned_be16(&power->value) * 1000000ULL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static u64 occ_get_powr_avg(u64 *accum, u32 *samples)
+{
+	return div64_u64(get_unaligned_be64(accum) * 1000000ULL,
+			 get_unaligned_be32(samples));
+}
+
+static ssize_t occ_show_power_2(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u64 val = 0;
+	struct power_sensor_2 *power;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	power = ((struct power_sensor_2 *)sensors->power.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		return snprintf(buf, PAGE_SIZE - 1, "%u_%u_%u\n",
+				get_unaligned_be32(&power->sensor_id),
+				power->function_id, power->apss_channel);
+	case 1:
+		val = occ_get_powr_avg(&power->accumulator,
+				       &power->update_tag);
+		break;
+	case 2:
+		val = get_unaligned_be32(&power->update_tag) *
+			occ->powr_sample_time_us;
+		break;
+	case 3:
+		val = get_unaligned_be16(&power->value) * 1000000ULL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static ssize_t occ_show_power_a0(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u64 val = 0;
+	struct power_sensor_a0 *power;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	power = ((struct power_sensor_a0 *)sensors->power.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		return snprintf(buf, PAGE_SIZE - 1, "%u_system\n",
+				get_unaligned_be32(&power->sensor_id));
+	case 1:
+		val = occ_get_powr_avg(&power->system.accumulator,
+				       &power->system.update_tag);
+		break;
+	case 2:
+		val = get_unaligned_be32(&power->system.update_tag) *
+			occ->powr_sample_time_us;
+		break;
+	case 3:
+		val = get_unaligned_be16(&power->system.value) * 1000000ULL;
+		break;
+	case 4:
+		return snprintf(buf, PAGE_SIZE - 1, "%u_proc\n",
+				get_unaligned_be32(&power->sensor_id));
+	case 5:
+		val = occ_get_powr_avg(&power->proc.accumulator,
+				       &power->proc.update_tag);
+		break;
+	case 6:
+		val = get_unaligned_be32(&power->proc.update_tag) *
+			occ->powr_sample_time_us;
+		break;
+	case 7:
+		val = get_unaligned_be16(&power->proc.value) * 1000000ULL;
+		break;
+	case 8:
+		return snprintf(buf, PAGE_SIZE - 1, "%u_vdd\n",
+				get_unaligned_be32(&power->sensor_id));
+	case 9:
+		val = occ_get_powr_avg(&power->vdd.accumulator,
+				       &power->vdd.update_tag);
+		break;
+	case 10:
+		val = get_unaligned_be32(&power->vdd.update_tag) *
+			occ->powr_sample_time_us;
+		break;
+	case 11:
+		val = get_unaligned_be16(&power->vdd.value) * 1000000ULL;
+		break;
+	case 12:
+		return snprintf(buf, PAGE_SIZE - 1, "%u_vdn\n",
+				get_unaligned_be32(&power->sensor_id));
+	case 13:
+		val = occ_get_powr_avg(&power->vdn.accumulator,
+				       &power->vdn.update_tag);
+		break;
+	case 14:
+		val = get_unaligned_be32(&power->vdn.update_tag) *
+			occ->powr_sample_time_us;
+		break;
+	case 15:
+		val = get_unaligned_be16(&power->vdn.value) * 1000000ULL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static ssize_t occ_show_caps_1_2(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u64 val = 0;
+	struct caps_sensor_2 *caps;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	caps = ((struct caps_sensor_2 *)sensors->caps.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		return snprintf(buf, PAGE_SIZE - 1, "system\n");
+	case 1:
+		val = get_unaligned_be16(&caps->cap) * 1000000ULL;
+		break;
+	case 2:
+		val = get_unaligned_be16(&caps->system_power) * 1000000ULL;
+		break;
+	case 3:
+		val = get_unaligned_be16(&caps->n_cap) * 1000000ULL;
+		break;
+	case 4:
+		val = get_unaligned_be16(&caps->max) * 1000000ULL;
+		break;
+	case 5:
+		val = get_unaligned_be16(&caps->min) * 1000000ULL;
+		break;
+	case 6:
+		val = get_unaligned_be16(&caps->user) * 1000000ULL;
+		break;
+	case 7:
+		if (occ->sensors.caps.version == 1)
+			return -EINVAL;
+
+		val = caps->user_source;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static ssize_t occ_show_caps_3(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u64 val = 0;
+	struct caps_sensor_3 *caps;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	caps = ((struct caps_sensor_3 *)sensors->caps.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		return snprintf(buf, PAGE_SIZE - 1, "system\n");
+	case 1:
+		val = get_unaligned_be16(&caps->cap) * 1000000ULL;
+		break;
+	case 2:
+		val = get_unaligned_be16(&caps->system_power) * 1000000ULL;
+		break;
+	case 3:
+		val = get_unaligned_be16(&caps->n_cap) * 1000000ULL;
+		break;
+	case 4:
+		val = get_unaligned_be16(&caps->max) * 1000000ULL;
+		break;
+	case 5:
+		val = get_unaligned_be16(&caps->hard_min) * 1000000ULL;
+		break;
+	case 6:
+		val = get_unaligned_be16(&caps->user) * 1000000ULL;
+		break;
+	case 7:
+		val = caps->user_source;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static ssize_t occ_store_caps_user(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	int rc;
+	u16 user_power_cap;
+	unsigned long long value;
+	struct occ *occ = dev_get_drvdata(dev);
+
+	rc = kstrtoull(buf, 0, &value);
+	if (rc)
+		return rc;
+
+	user_power_cap = div64_u64(value, 1000000ULL); /* microwatt to watt */
+
+	rc = occ_set_user_power_cap(occ, user_power_cap);
+	if (rc)
+		return rc;
+
+	return count;
+}
+
+static ssize_t occ_show_extended(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	int rc;
+	struct extended_sensor *extn;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	extn = ((struct extended_sensor *)sensors->extended.data) +
+		sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		if (extn->flags & EXTN_FLAG_SENSOR_ID)
+			rc = snprintf(buf, PAGE_SIZE - 1, "%u",
+				      get_unaligned_be32(&extn->sensor_id));
+		else
+			rc = snprintf(buf, PAGE_SIZE - 1, "%02x%02x%02x%02x\n",
+				      extn->name[0], extn->name[1],
+				      extn->name[2], extn->name[3]);
+		break;
+	case 1:
+		rc = snprintf(buf, PAGE_SIZE - 1, "%02x\n", extn->flags);
+		break;
+	case 2:
+		rc = snprintf(buf, PAGE_SIZE - 1, "%02x%02x%02x%02x%02x%02x\n",
+			      extn->data[0], extn->data[1], extn->data[2],
+			      extn->data[3], extn->data[4], extn->data[5]);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
 /* only need to do this once at startup, as OCC won't change sensors on us */
 static void occ_parse_poll_response(struct occ *occ)
 {
@@ -85,6 +703,9 @@ int occ_setup(struct occ *occ, const char *name)
 {
 	int rc;
 
+	mutex_init(&occ->lock);
+
+	/* no need to lock */
 	rc = occ_poll(occ);
 	if (rc == -ESHUTDOWN) {
 		dev_info(occ->bus_dev, "host is not ready\n");
drivers/hwmon/occ/common.h +6 -0
index 0a7a107..e074251 100644
@@ -3,6 +3,8 @@
 #ifndef OCC_COMMON_H
 #define OCC_COMMON_H
 
+#include <linux/mutex.h>
+
 struct device;
 
 #define OCC_RESP_DATA_BYTES		4089
@@ -80,8 +82,12 @@ struct occ {
 	struct occ_response resp;
 	struct occ_sensors sensors;
 
+	int powr_sample_time_us;	/* average power sample time */
 	u8 poll_cmd_data;		/* to perform OCC poll command */
 	int (*send_cmd)(struct occ *occ, u8 *cmd);
+
+	unsigned long last_update;
+	struct mutex lock;		/* lock OCC access */
 };
 
 int occ_setup(struct occ *occ, const char *name);
drivers/hwmon/occ/p8_i2c.c +1 -0
index e3326ff..e4c2c04 100644
@@ -216,6 +216,7 @@ static int p8_i2c_occ_probe(struct i2c_client *client,
 	occ->bus_dev = &client->dev;
 	dev_set_drvdata(&client->dev, occ);
 
+	occ->powr_sample_time_us = 250;
 	occ->poll_cmd_data = 0x10;		/* P8 OCC poll data */
 	occ->send_cmd = p8_i2c_occ_send_cmd;
 
drivers/hwmon/occ/p9_sbe.c +1 -0
index 0ed5e22..b33f4fe 100644
@@ -69,6 +69,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 	occ->bus_dev = &pdev->dev;
 	platform_set_drvdata(pdev, occ);
 
+	occ->powr_sample_time_us = 500;
 	occ->poll_cmd_data = 0x20;		/* P9 OCC poll data */
 	occ->send_cmd = p9_sbe_occ_send_cmd;
 
Signature · 1 line
1.8.3.1
author maintainer
#9 lore raw

PATCH v6 9/10 hwmon (occ): Add sensor attributes and register hwmon device

In reply to Eddie James

From: Eddie James <[email protected]>

Setup the sensor attributes for every OCC sensor found by the first poll response. Register the attributes with hwmon.

---
 drivers/hwmon/occ/common.c | 337 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h |  16 +++
 2 files changed, 353 insertions(+)
drivers/hwmon/occ/common.c +337 -0
index f7220132..c6c8161 100644
@@ -1,11 +1,13 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/device.h>
+#include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/math64.h>
 #include <linux/mutex.h>
+#include <linux/sysfs.h>
 #include <asm/unaligned.h>
 
 #include "common.h"
@@ -641,6 +643,324 @@ static ssize_t occ_show_extended(struct device *dev,
 	return rc;
 }
 
+/*
+ * Some helper macros to make it easier to define an occ_attribute. Since these
+ * are dynamically allocated, we shouldn't use the existing kernel macros which
+ * stringify the name argument.
+ */
+#define ATTR_OCC(_name, _mode, _show, _store) {				\
+	.attr	= {							\
+		.name = _name,						\
+		.mode = VERIFY_OCTAL_PERMISSIONS(_mode),		\
+	},								\
+	.show	= _show,						\
+	.store	= _store,						\
+}
+
+#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) {	\
+	.dev_attr	= ATTR_OCC(_name, _mode, _show, _store),	\
+	.index		= _index,					\
+	.nr		= _nr,						\
+}
+
+#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index)		\
+	((struct sensor_device_attribute_2)				\
+		SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index))
+
+/*
+ * Allocate and instatiate sensor_device_attribute_2s. It's most efficient to
+ * use our own instead of the built-in hwmon attribute types.
+ */
+static int occ_setup_sensor_attrs(struct occ *occ)
+{
+	unsigned int i, s, num_attrs = 0;
+	struct device *dev = occ->bus_dev;
+	struct occ_sensors *sensors = &occ->sensors;
+	struct occ_attribute *attr;
+	struct temp_sensor_2 *temp;
+	ssize_t (*show_temp)(struct device *, struct device_attribute *,
+			     char *) = occ_show_temp_1;
+	ssize_t (*show_freq)(struct device *, struct device_attribute *,
+			     char *) = occ_show_freq_1;
+	ssize_t (*show_power)(struct device *, struct device_attribute *,
+			      char *) = occ_show_power_1;
+	ssize_t (*show_caps)(struct device *, struct device_attribute *,
+			     char *) = occ_show_caps_1_2;
+
+	switch (sensors->temp.version) {
+	case 1:
+		num_attrs += (sensors->temp.num_sensors * 2);
+		break;
+	case 2:
+		num_attrs += (sensors->temp.num_sensors * 4);
+		show_temp = occ_show_temp_2;
+		break;
+	default:
+		sensors->temp.num_sensors = 0;
+	}
+
+	switch (sensors->freq.version) {
+	case 2:
+		show_freq = occ_show_freq_2;
+		/* fall through */
+	case 1:
+		num_attrs += (sensors->freq.num_sensors * 2);
+		break;
+	default:
+		sensors->freq.num_sensors = 0;
+	}
+
+	switch (sensors->power.version) {
+	case 2:
+		show_power = occ_show_power_2;
+		/* fall through */
+	case 1:
+		num_attrs += (sensors->power.num_sensors * 4);
+		break;
+	case 0xA0:
+		num_attrs += (sensors->power.num_sensors * 16);
+		show_power = occ_show_power_a0;
+		break;
+	default:
+		sensors->power.num_sensors = 0;
+	}
+
+	switch (sensors->caps.version) {
+	case 1:
+		num_attrs += (sensors->caps.num_sensors * 7);
+		break;
+	case 3:
+		show_caps = occ_show_caps_3;
+		/* fall through */
+	case 2:
+		num_attrs += (sensors->caps.num_sensors * 8);
+		break;
+	default:
+		sensors->caps.num_sensors = 0;
+	}
+
+	switch (sensors->extended.version) {
+	case 1:
+		num_attrs += (sensors->extended.num_sensors * 3);
+		break;
+	default:
+		sensors->extended.num_sensors = 0;
+	}
+
+	occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * num_attrs,
+				  GFP_KERNEL);
+	if (!occ->attrs)
+		return -ENOMEM;
+
+	/* null-terminated list */
+	occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) *
+					num_attrs + 1, GFP_KERNEL);
+	if (!occ->group.attrs)
+		return -ENOMEM;
+
+	attr = occ->attrs;
+
+	for (i = 0; i < sensors->temp.num_sensors; ++i) {
+		s = i + 1;
+		temp = ((struct temp_sensor_2 *)sensors->temp.data) + i;
+
+		snprintf(attr->name, sizeof(attr->name), "temp%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
+					     0, i);
+		attr++;
+
+		if (sensors->temp.version > 1 &&
+		    temp->fru_type == OCC_FRU_TYPE_VRM) {
+			snprintf(attr->name, sizeof(attr->name),
+				 "temp%d_alarm", s);
+		} else {
+			snprintf(attr->name, sizeof(attr->name),
+				 "temp%d_input", s);
+		}
+
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL,
+					     1, i);
+		attr++;
+
+		if (sensors->temp.version > 1) {
+			snprintf(attr->name, sizeof(attr->name),
+				 "temp%d_fru_type", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_temp, NULL, 2, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "temp%d_fault", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_temp, NULL, 3, i);
+			attr++;
+		}
+	}
+
+	for (i = 0; i < sensors->freq.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "freq%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
+					     0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "freq%d_input", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL,
+					     1, i);
+		attr++;
+	}
+
+	if (sensors->power.version == 0xA0) {
+		/*
+		 * Special case for many-attribute power sensor. Split it into
+		 * a sensor number per power type, emulating several sensors.
+		 */
+		for (i = 0; i < sensors->power.num_sensors; ++i) {
+			unsigned int j;
+			unsigned int nr = 0;
+
+			s = (i * 4) + 1;
+
+			for (j = 0; j < 4; ++j) {
+				snprintf(attr->name, sizeof(attr->name),
+					 "power%d_label", s);
+				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+							     show_power, NULL,
+							     nr++, i);
+				attr++;
+
+				snprintf(attr->name, sizeof(attr->name),
+					 "power%d_average", s);
+				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+							     show_power, NULL,
+							     nr++, i);
+				attr++;
+
+				snprintf(attr->name, sizeof(attr->name),
+					 "power%d_average_interval", s);
+				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+							     show_power, NULL,
+							     nr++, i);
+				attr++;
+
+				snprintf(attr->name, sizeof(attr->name),
+					 "power%d_input", s);
+				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+							     show_power, NULL,
+							     nr++, i);
+				attr++;
+
+				s++;
+			}
+		}
+	} else {
+		for (i = 0; i < sensors->power.num_sensors; ++i) {
+			s = i + 1;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_label", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 0, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_average", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 1, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_average_interval", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 2, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_input", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 3, i);
+			attr++;
+		}
+	}
+
+	if (sensors->caps.num_sensors >= 1) {
+		s = sensors->power.num_sensors + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     0, 0);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_cap", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     1, 0);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_input", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     2, 0);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name),
+			 "power%d_cap_not_redundant", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     3, 0);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_cap_max", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     4, 0);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_cap_min", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     5, 0);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_cap_user",
+			 s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps,
+					     occ_store_caps_user, 6, 0);
+		attr++;
+
+		if (sensors->caps.version > 1) {
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_cap_user_source", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_caps, NULL, 7, 0);
+			attr++;
+		}
+	}
+
+	for (i = 0; i < sensors->extended.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "extn%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     occ_show_extended, NULL, 0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "extn%d_flags", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     occ_show_extended, NULL, 1, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "extn%d_input", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     occ_show_extended, NULL, 2, i);
+		attr++;
+	}
+
+	/* put the sensors in the group */
+	for (i = 0; i < num_attrs; ++i) {
+		sysfs_attr_init(&occ->attrs[i].sensor.dev_attr.attr);
+		occ->group.attrs[i] = &occ->attrs[i].sensor.dev_attr.attr;
+	}
+
+	return 0;
+}
+
 /* only need to do this once at startup, as OCC won't change sensors on us */
 static void occ_parse_poll_response(struct occ *occ)
 {
@@ -704,6 +1024,7 @@ int occ_setup(struct occ *occ, const char *name)
 	int rc;
 
 	mutex_init(&occ->lock);
+	occ->groups[0] = &occ->group;
 
 	/* no need to lock */
 	rc = occ_poll(occ);
@@ -718,5 +1039,21 @@ int occ_setup(struct occ *occ, const char *name)
 
 	occ_parse_poll_response(occ);
 
+	rc = occ_setup_sensor_attrs(occ);
+	if (rc) {
+		dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n",
+			rc);
+		return rc;
+	}
+
+	occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name,
+							    occ, occ->groups);
+	if (IS_ERR(occ->hwmon)) {
+		rc = PTR_ERR(occ->hwmon);
+		dev_err(occ->bus_dev, "failed to register hwmon device: %d\n",
+			rc);
+		return rc;
+	}
+
 	return 0;
 }
drivers/hwmon/occ/common.h +16 -0
index e074251..00ac101 100644
@@ -3,7 +3,9 @@
 #ifndef OCC_COMMON_H
 #define OCC_COMMON_H
 
+#include <linux/hwmon-sysfs.h>
 #include <linux/mutex.h>
+#include <linux/sysfs.h>
 
 struct device;
 
@@ -76,6 +78,15 @@ struct occ_sensors {
 	struct occ_sensor extended;
 };
 
+/*
+ * Use our own attribute struct so we can dynamically allocate space for the
+ * name.
+ */
+struct occ_attribute {
+	char name[32];
+	struct sensor_device_attribute_2 sensor;
+};
+
 struct occ {
 	struct device *bus_dev;
 
@@ -88,6 +99,11 @@ struct occ {
 
 	unsigned long last_update;
 	struct mutex lock;		/* lock OCC access */
+
+	struct device *hwmon;
+	struct occ_attribute *attrs;
+	struct attribute_group group;
+	const struct attribute_group *groups[2];
 };
 
 int occ_setup(struct occ *occ, const char *name);
Signature · 1 line
1.8.3.1
author maintainer
#10 lore raw

PATCH v6 10/10 hwmon (occ): Add sysfs attributes for additional OCC data

In reply to Eddie James

From: Eddie James <[email protected]>

The OCC provides a variety of additional information about the state of the host processor, such as throttling, error conditions, and the number of OCCs detected in the system. This information is essential to service processor applications such as fan control and host management. Therefore, export this data in the form of sysfs attributes attached to the platform device (to which the hwmon device is also attached).

---
 drivers/hwmon/occ/Makefile |   4 +-
 drivers/hwmon/occ/common.c |  45 ++++++++++-
 drivers/hwmon/occ/common.h |  17 ++++
 drivers/hwmon/occ/p8_i2c.c |  10 +++
 drivers/hwmon/occ/p9_sbe.c |   1 +
 drivers/hwmon/occ/sysfs.c  | 188 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 260 insertions(+), 5 deletions(-)
 create mode 100644 drivers/hwmon/occ/sysfs.c
drivers/hwmon/occ/Makefile +2 -2
index 57c0e91..3fec12d 100644
@@ -1,5 +1,5 @@
-occ-p8-hwmon-objs := common.o p8_i2c.o
-occ-p9-hwmon-objs := common.o p9_sbe.o
+occ-p8-hwmon-objs := common.o sysfs.o p8_i2c.o
+occ-p9-hwmon-objs := common.o sysfs.o p9_sbe.o
 
 obj-$(CONFIG_SENSORS_OCC_P8_I2C) += occ-p8-hwmon.o
 obj-$(CONFIG_SENSORS_OCC_P9_SBE) += occ-p9-hwmon.o
drivers/hwmon/occ/common.c +42 -3

75 lines not on this page · raw message

drivers/hwmon/occ/common.h +17 -0
index 00ac101..da80e65 100644
@@ -104,8 +104,25 @@ struct occ {
 	struct occ_attribute *attrs;
 	struct attribute_group group;
 	const struct attribute_group *groups[2];
+
+	int error;                      /* latest transfer error */
+	unsigned int error_count;       /* number of xfr errors observed */
+	unsigned long last_safe;        /* time OCC entered "safe" state */
+
+	/*
+	 * Store the previous state data for comparison in order to notify
+	 * sysfs readers of state changes.
+	 */
+	int prev_error;
+	u8 prev_stat;
+	u8 prev_ext_stat;
+	u8 prev_occs_present;
 };
 
 int occ_setup(struct occ *occ, const char *name);
+int occ_setup_sysfs(struct occ *occ);
+void occ_shutdown(struct occ *occ);
+void occ_sysfs_poll_done(struct occ *occ);
+int occ_update_response(struct occ *occ);
 
 #endif /* OCC_COMMON_H */
drivers/hwmon/occ/p8_i2c.c +10 -0
index e4c2c04..b59efc9 100644
@@ -223,6 +223,15 @@ static int p8_i2c_occ_probe(struct i2c_client *client,
 	return occ_setup(occ, "p8_occ");
 }
 
+static int p8_i2c_occ_remove(struct i2c_client *client)
+{
+	struct occ *occ = dev_get_drvdata(&client->dev);
+
+	occ_shutdown(occ);
+
+	return 0;
+}
+
 static const struct of_device_id p8_i2c_occ_of_match[] = {
 	{ .compatible = "ibm,p8-occ-hwmon" },
 	{}
@@ -236,6 +245,7 @@ static int p8_i2c_occ_probe(struct i2c_client *client,
 		.of_match_table = p8_i2c_occ_of_match,
 	},
 	.probe = p8_i2c_occ_probe,
+	.remove = p8_i2c_occ_remove,
 };
 
 module_i2c_driver(p8_i2c_occ_driver);
drivers/hwmon/occ/p9_sbe.c +1 -0
index b33f4fe..b65c1d1 100644
@@ -86,6 +86,7 @@ static int p9_sbe_occ_remove(struct platform_device *pdev)
 	struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ);
 
 	ctx->sbe = NULL;
+	occ_shutdown(occ);
 
 	return 0;
 }
drivers/hwmon/occ/sysfs.c new file +188 -0

188 lines not on this page · raw message

Signature · 1 line
1.8.3.1
maintainer
#11 lore raw
In reply to Eddie James
Quoted 6 lines · Eddie James

From: Eddie James <[email protected]>

This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management.

Quoted 9 lines

The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already available as an FSI client driver.

For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus.

I am not entirely happy with the series - there are still lots of proprietary attributes, and I would have preferred the use of the _info API at this point - but this has taken long enough. Series applied to hwmon-next. Please send any fixes as follow-up patches.

Thanks,
Guenter

Quoted 86 lines

Changes since v5:
* Makefile fix when compiling both P8 and P9 versions
* Spelling fix in hwmon doc
* Added an additional sentence for P9 binding doc to explain that OCC isn't an FSI slave device.

Changes since v4:
* Make the hwmon attributes conform almost completely to standard names and values. The only exception is powerX_cap_user and powerX_cap_user_source.
* Improve hwmon documentation.
* Add ibm,p9-occ dt documentation.

Changes since v3:
* Add the FSI OCC driver.
* Pull the sysfs attribute code into it's own file for cleanliness.
* Various fixes for attribute creation and integer overflow.

Changes since v2:
* Add sysfs_notify for the error and throttling attributes when change is detected.
* Removed occs_present counting of devices bound.
* Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound.
* Added default cases (return EINVAL) for all sensor show functions.
* Added temperature fault sensor.
* Added back dt binding documentation for P9 to address checkpatch warning.
* Added occs_present attribute from the poll response.

Changes since v1:
* Remove wait loop in P9 code, as that is now handled by FSI OCC driver.
* Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically.
* Moved OCC response code definitions to the OCC include file.
* Fixed includes.
* Changed some structure fields to __beXX as that is what they are.
* Changed some errnos.
* Removed some dev_err().
* Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop.
* Refactored error handling a bit.

Eddie James (10):
dt-bindings: fsi: Add P9 OCC device documentation
fsi: Add On-Chip Controller (OCC) driver
Documentation: hwmon: Add OCC documentation
dt-bindings: i2c: Add P8 OCC hwmon device documentation
hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response
hwmon (occ): Add sensor types and versions
hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data

 .../devicetree/bindings/fsi/ibm,p9-occ.txt         |   16 +
 .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt   |   25 +
 Documentation/hwmon/occ                            |  112 ++
 drivers/fsi/Kconfig                                |   10 +
 drivers/fsi/Makefile                               |    1 +
 drivers/fsi/fsi-occ.c                              |  599 +++++++++++
 drivers/hwmon/Kconfig                              |    2 +
 drivers/hwmon/Makefile                             |    1 +
 drivers/hwmon/occ/Kconfig                          |   31 +
 drivers/hwmon/occ/Makefile                         |    5 +
 drivers/hwmon/occ/common.c                         | 1098 ++++++++++++++++++++
 drivers/hwmon/occ/common.h                         |  128 +++
 drivers/hwmon/occ/p8_i2c.c                         |  255 +++++
 drivers/hwmon/occ/p9_sbe.c                         |  106 ++
 drivers/hwmon/occ/sysfs.c                          |  188 ++++
 include/linux/fsi-occ.h                            |   25 +
 16 files changed, 2602 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt
 create mode 100644 Documentation/hwmon/occ
 create mode 100644 drivers/fsi/fsi-occ.c
 create mode 100644 drivers/hwmon/occ/Kconfig
 create mode 100644 drivers/hwmon/occ/Makefile
 create mode 100644 drivers/hwmon/occ/common.c
 create mode 100644 drivers/hwmon/occ/common.h
 create mode 100644 drivers/hwmon/occ/p8_i2c.c
 create mode 100644 drivers/hwmon/occ/p9_sbe.c
 create mode 100644 drivers/hwmon/occ/sysfs.c
 create mode 100644 include/linux/fsi-occ.h
Signature · 1 line
1.8.3.1
#12 lore raw
Quoted 13 lines · Guenter Roeck
Quoted 12 lines · Eddie James

From: Eddie James <[email protected]>

This series adds a hwmon driver to support the OCC on POWER8 and POWER9 processors. The OCC is an embedded processor that provides realtime power and thermal monitoring and management.

The series also adds a "bus" driver to handle atomic communication between the service processor and the OCC on a POWER9 chip. This communication takes place over FSI bus to the SBE (Self-Boot engine) FIFO, which in turn communicates with the OCC. The driver for the SBEFIFO is already available as an FSI client driver.

Quoted 7 lines
Quoted 3 lines

For POWER8 OCCs, communication between the service processor and the OCC is achieved over I2C bus.

I am not entirely happy with the series - there are still lots of proprietary attributes, and I would have preferred the use of the _info API at this point - but this has taken long enough. Series applied to hwmon-next. Please send any fixes as follow-up patches.

Thanks a lot Guenter, appreciate your help getting here.

Eddie

Quoted 90 lines

Thanks,
Guenter

Quoted 86 lines

Changes since v5:
* Makefile fix when compiling both P8 and P9 versions
* Spelling fix in hwmon doc
* Added an additional sentence for P9 binding doc to explain that OCC isn't an FSI slave device.

Changes since v4:
* Make the hwmon attributes conform almost completely to standard names and values. The only exception is powerX_cap_user and powerX_cap_user_source.
* Improve hwmon documentation.
* Add ibm,p9-occ dt documentation.

Changes since v3:
* Add the FSI OCC driver.
* Pull the sysfs attribute code into it's own file for cleanliness.
* Various fixes for attribute creation and integer overflow.

Changes since v2:
* Add sysfs_notify for the error and throttling attributes when change is detected.
* Removed occs_present counting of devices bound.
* Improved remove() of P9 driver to avoid bad behavior with relation to OCC driver when unbound.
* Added default cases (return EINVAL) for all sensor show functions.
* Added temperature fault sensor.
* Added back dt binding documentation for P9 to address checkpatch warning.
* Added occs_present attribute from the poll response.

Changes since v1:
* Remove wait loop in P9 code, as that is now handled by FSI OCC driver.
* Removed dt binding documentation for P9, FSI OCC driver will probe OCC hwmon driver automatically.
* Moved OCC response code definitions to the OCC include file.
* Fixed includes.
* Changed some structure fields to __beXX as that is what they are.
* Changed some errnos.
* Removed some dev_err().
* Refactored P8 code a bit to use #defined addresses and magic values, and changed "goto retry" to a loop.
* Refactored error handling a bit.

Eddie James (10):
dt-bindings: fsi: Add P9 OCC device documentation
fsi: Add On-Chip Controller (OCC) driver
Documentation: hwmon: Add OCC documentation
dt-bindings: i2c: Add P8 OCC hwmon device documentation
hwmon: Add On-Chip Controller (OCC) hwmon driver hwmon (occ): Add command transport method for P8 and P9 hwmon (occ): Parse OCC poll response
hwmon (occ): Add sensor types and versions
hwmon (occ): Add sensor attributes and register hwmon device hwmon (occ): Add sysfs attributes for additional OCC data

  .../devicetree/bindings/fsi/ibm,p9-occ.txt         |   16 +
  .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt   |   25 +
  Documentation/hwmon/occ                            |  112 ++
  drivers/fsi/Kconfig                                |   10 +
  drivers/fsi/Makefile                               |    1 +
  drivers/fsi/fsi-occ.c                              |  599 +++++++++++
  drivers/hwmon/Kconfig                              |    2 +
  drivers/hwmon/Makefile                             |    1 +
  drivers/hwmon/occ/Kconfig                          |   31 +
  drivers/hwmon/occ/Makefile                         |    5 +
  drivers/hwmon/occ/common.c                         | 1098 ++++++++++++++++++++
  drivers/hwmon/occ/common.h                         |  128 +++
  drivers/hwmon/occ/p8_i2c.c                         |  255 +++++
  drivers/hwmon/occ/p9_sbe.c                         |  106 ++
  drivers/hwmon/occ/sysfs.c                          |  188 ++++
  include/linux/fsi-occ.h                            |   25 +
  16 files changed, 2602 insertions(+)

create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt create mode 100644 Documentation/hwmon/occ
create mode 100644 drivers/fsi/fsi-occ.c
create mode 100644 drivers/hwmon/occ/Kconfig
create mode 100644 drivers/hwmon/occ/Makefile
create mode 100644 drivers/hwmon/occ/common.c
create mode 100644 drivers/hwmon/occ/common.h
create mode 100644 drivers/hwmon/occ/p8_i2c.c
create mode 100644 drivers/hwmon/occ/p9_sbe.c
create mode 100644 drivers/hwmon/occ/sysfs.c
create mode 100644 include/linux/fsi-occ.h

Signature · 1 line
1.8.3.1