All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver
@ 2017-11-20 23:53 Eddie James
  2017-11-20 23:53 ` [PATCH v3 01/12] Documentation: hwmon: Add OCC documentation Eddie James
                   ` (11 more replies)
  0 siblings, 12 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

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.

This driver has two different platform drivers as a "base" for the
hwmon interface, as the means of communicating with the OCC on P8 and P9 is
completely different. For P8, the driver is an I2C client driver. For P9 the
driver is an FSI-based OCC client driver, and uses the OCC driver in-kernel
API. The OCC driver is on the LKML (latest https://lkml.org/lkml/2017/11/20/631).

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.

Edward A. James (12):
  Documentation: hwmon: Add OCC documentation
  Documentation: ABI: Add occ-hwmon driver sysfs documentation
  dt-bindings: i2c: Add P8 OCC hwmon device documentation
  dt-bindings: fsi: Add P9 OCC hwmon device documentation
  hwmon (occ): 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 non-hwmon attributes
  hwmon (occ): Add error handling
  hwmon (occ): Add sysfs notification for errors and throttling

 Documentation/ABI/testing/sysfs-driver-occ-hwmon   |   85 ++
 .../devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt   |   16 +
 .../devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt   |   25 +
 Documentation/hwmon/occ                            |   75 ++
 drivers/hwmon/Kconfig                              |    2 +
 drivers/hwmon/Makefile                             |    1 +
 drivers/hwmon/occ/Kconfig                          |   28 +
 drivers/hwmon/occ/Makefile                         |   11 +
 drivers/hwmon/occ/common.c                         | 1387 ++++++++++++++++++++
 drivers/hwmon/occ/common.h                         |  124 ++
 drivers/hwmon/occ/p8_i2c.c                         |  262 ++++
 drivers/hwmon/occ/p9_sbe.c                         |  161 +++
 12 files changed, 2177 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-occ-hwmon
 create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
 create mode 100644 Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt
 create mode 100644 Documentation/hwmon/occ
 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

-- 
1.8.3.1

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v3 01/12] Documentation: hwmon: Add OCC documentation
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-20 23:53 ` [PATCH v3 02/12] Documentation: ABI: Add occ-hwmon driver sysfs documentation Eddie James
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Document the hwmon interface for the OCC.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 Documentation/hwmon/occ | 75 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 Documentation/hwmon/occ

diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ
new file mode 100644
index 0000000..c88d0f5
--- /dev/null
+++ b/Documentation/hwmon/occ
@@ -0,0 +1,75 @@
+Kernel driver occ-hwmon
+=======================
+
+Supported chips:
+  * POWER8
+  * POWER9
+
+Author: Eddie James <eajames@us.ibm.com>
+
+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. Please see the
+device-tree bindings documentation for that driver for details on probing
+an OCC device (Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt).
+
+Sysfs entries
+-------------
+
+The following attributes are supported. All attributes are read-only unless
+specified.
+
+temp[1-n]_label		OCC sensor id.
+temp[1-n]_input		Measured temperature in millidegrees C.
+[with temperature sensor version 2+]
+    temp[1-n]_fru_type		Given FRU (Field Replaceable Unit) type.
+    temp[1-n]_fault		Temperature sensor fault.
+
+freq[1-n]_label		OCC sensor id.
+freq[1-n]_input		Measured frequency.
+
+power[1-n]_label	OCC sensor id.
+power[1-n]_input	Measured power in microwatts.
+power[1-n]_update_tag	Number of 250us samples represented in accumulator.
+power[1-n]_accumulator	Accumulation of 250us power readings.
+[with power sensor version 2+]
+    power[1-n]_function_id	Identifies what the power reading is for.
+    power[1-n]_apss_channel	Indicates APSS channel.
+
+[power version 0xa0 only]
+power1_id			OCC sensor id.
+power[1-n]_label		Sensor type, "system", "proc", "vdd", or "vdn".
+power[1-n]_input		Most recent power reading in microwatts.
+power[1-n]_update_tag		Number of samples in the accumulator.
+power[1-n]_accumulator		Accumulation of power readings.
+[with sensor type "system" and "proc" only]
+    power[1-n]_update_time	Time in us that the power value is read.
+
+caps1_current		Current OCC power cap in watts.
+caps1_reading		Current system output power in watts.
+caps1_norm		Power cap without redundant power.
+caps1_max		Maximum power cap.
+[caps version 1 and 2 only]
+    caps1_min		Minimum power cap.
+[caps version 3+]
+    caps1_min_hard		Hard minimum cap that can be set and held.
+    caps1_min_soft		Soft minimum cap below hard, not guaranteed.
+caps1_user		The powercap specified by the user. Will be 0 if no
+			user powercap exists. This attribute is read-write.
+[caps version 1+]
+    caps1_user_source	Indicates how the user power limit was set.
+
+extn[1-n]_label		ASCII id or sensor id.
+extn[1-n]_flags		Indicates type of label attribute.
+extn[1-n]_input		Data.
-- 
1.8.3.1

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 02/12] Documentation: ABI: Add occ-hwmon driver sysfs documentation
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
  2017-11-20 23:53 ` [PATCH v3 01/12] Documentation: hwmon: Add OCC documentation Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-22 15:15   ` [v3, " Guenter Roeck
  2017-11-20 23:53 ` [PATCH v3 03/12] dt-bindings: i2c: Add P8 OCC hwmon device documentation Eddie James
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Detail the sysfs attributes provided by the occ-hwmon driver.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 Documentation/ABI/testing/sysfs-driver-occ-hwmon | 85 ++++++++++++++++++++++++
 1 file changed, 85 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-occ-hwmon

diff --git a/Documentation/ABI/testing/sysfs-driver-occ-hwmon b/Documentation/ABI/testing/sysfs-driver-occ-hwmon
new file mode 100644
index 0000000..8873cc3
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-occ-hwmon
@@ -0,0 +1,85 @@
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_active
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates (with a "1" or a "0",
+		respectively) whether or not this OCC is in the "active" state.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_dvfs_ot
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates (with a "1" or a "0",
+		respectively) whether or not this OCC has limited the processor
+		frequency due to over-temperature.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_dvfs_power
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates (with a "1" or a "0",
+		respectively) whether or not this OCC has limited the processor
+		frequency due to power usage.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_error
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates any error condition
+		observed by the OCC or detected by the driver. Reading the
+		attribute will return an integer. A negative integer indicates
+		either an error response from the OCC or bus error or other
+		error condition detected by the driver. A "0" indicates no
+		error.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_master
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates (with a "1" or a "0",
+		respectively) whether or not this OCC is the "master" OCC.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_mem_throttle
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates (with a "1" or a "0",
+		respectively) whether or not the OCC has throttled memory due
+		to over-temperature.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occs_present
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates the number of OCCs present
+		on the system.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_quick_drop
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates (with a "1" or a "0",
+		respectively) whether or not this OCC has asserted the "quick
+		power drop" signal.
+
+What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_status
+Date:		November 2017
+KernelVersion:	4.14
+Contact:	eajames@us.ibm.com
+Description:
+		A read-only attribute that indicates the current OCC state. The
+		value of the attribute will be one of the following states:
+		0: Reserved
+		1: Standby
+		2: Observation
+		3: Active
+		4: Safe
+		5: Characterization
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 03/12] dt-bindings: i2c: Add P8 OCC hwmon device documentation
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
  2017-11-20 23:53 ` [PATCH v3 01/12] Documentation: hwmon: Add OCC documentation Eddie James
  2017-11-20 23:53 ` [PATCH v3 02/12] Documentation: ABI: Add occ-hwmon driver sysfs documentation Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-21 18:34   ` Rob Herring
  2017-11-20 23:53 ` [PATCH v3 04/12] dt-bindings: fsi: Add P9 " Eddie James
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Document the bindings for I2C-based OCC hwmon device.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 .../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

diff --git a/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt
new file mode 100644
index 0000000..5dc5d2e
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/ibm,p8-occ-hwmon.txt
@@ -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>;
+        };
+    };
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 04/12] dt-bindings: fsi: Add P9 OCC hwmon device documentation
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (2 preceding siblings ...)
  2017-11-20 23:53 ` [PATCH v3 03/12] dt-bindings: i2c: Add P8 OCC hwmon device documentation Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-21 18:36     ` Rob Herring
  2017-11-20 23:53 ` [PATCH v3 05/12] hwmon: Add On-Chip Controller (OCC) hwmon driver Eddie James
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Document the bindings for the FSI-based OCC hwmon device.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 .../devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt         | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt

diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
new file mode 100644
index 0000000..086b1eb
--- /dev/null
+++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
@@ -0,0 +1,16 @@
+Device-tree bindings for FSI-based On-Chip Controller hwmon device
+------------------------------------------------------------------
+
+Required properties:
+ - compatible = "ibm,p9-occ-hwmon";
+
+Examples:
+
+    occ@1 {
+        compatible = "ibm,p9-occ";
+        reg = <1>;
+
+        occ-hwmon@1 {
+            compatible = "ibm,p9-occ-hmwon";
+        }
+    };
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 05/12] hwmon: Add On-Chip Controller (OCC) hwmon driver
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (3 preceding siblings ...)
  2017-11-20 23:53 ` [PATCH v3 04/12] dt-bindings: fsi: Add P9 " Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-20 23:53   ` Eddie James
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

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.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/Kconfig      |  2 ++
 drivers/hwmon/Makefile     |  1 +
 drivers/hwmon/occ/Kconfig  | 28 +++++++++++++++++++
 drivers/hwmon/occ/Makefile | 11 ++++++++
 drivers/hwmon/occ/common.c | 44 +++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h | 40 +++++++++++++++++++++++++++
 drivers/hwmon/occ/p8_i2c.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/p9_sbe.c | 66 ++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 261 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

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 7ad0176..5043a33 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1273,6 +1273,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
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 0fe489f..596828c 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -173,6 +173,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
diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig
new file mode 100644
index 0000000..aa39de9
--- /dev/null
+++ b/drivers/hwmon/occ/Kconfig
@@ -0,0 +1,28 @@
+#
+# On-Chip Controller configuration
+#
+
+config SENSORS_OCC
+	tristate "POWER On-Chip Controller"
+	---help---
+	This option enables support for monitoring a variety of system sensors
+	provided by the On-Chip Controller (OCC) on a POWER processor.
+
+	This driver can also be built as a module. If so, the module will be
+	called occ-hwmon.
+
+config SENSORS_OCC_P8_I2C
+	bool "POWER8 OCC through I2C"
+	depends on I2C && SENSORS_OCC
+	---help---
+	This option enables support for monitoring sensors provided by the OCC
+	on a POWER8 processor. Communications with the OCC are established
+	through I2C bus.
+
+config SENSORS_OCC_P9_SBE
+	bool "POWER9 OCC through SBE"
+	depends on FSI_OCC && SENSORS_OCC
+	---help---
+	This option enables support for monitoring sensors provided by the OCC
+	on a POWER9 processor. Communications with the OCC are established
+	through SBE engine on an FSI bus.
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
new file mode 100644
index 0000000..ab5c3e9
--- /dev/null
+++ b/drivers/hwmon/occ/Makefile
@@ -0,0 +1,11 @@
+occ-hwmon-objs := common.o
+
+ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y)
+occ-hwmon-objs += p9_sbe.o
+endif
+
+ifeq ($(CONFIG_SENSORS_OCC_P8_I2C), y)
+occ-hwmon-objs += p8_i2c.o
+endif
+
+obj-$(CONFIG_SENSORS_OCC) += occ-hwmon.o
diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
new file mode 100644
index 0000000..92f06ae
--- /dev/null
+++ b/drivers/hwmon/occ/common.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#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 < 0) {
+		dev_err(occ->bus_dev, "failed to get OCC poll response: %d\n",
+			rc);
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
new file mode 100644
index 0000000..705dfe3
--- /dev/null
+++ b/drivers/hwmon/occ/common.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#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 */
diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
new file mode 100644
index 0000000..025471f
--- /dev/null
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include "common.h"
+
+struct p8_i2c_occ {
+	struct occ occ;
+	struct i2c_client *client;
+};
+
+#define to_p8_i2c_occ(x)	container_of((x), struct p8_i2c_occ, occ)
+
+static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd)
+{
+	return -EOPNOTSUPP;
+}
+
+static int p8_i2c_occ_probe(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	struct occ *occ;
+	struct p8_i2c_occ *p8_i2c_occ = devm_kzalloc(&client->dev,
+						     sizeof(*p8_i2c_occ),
+						     GFP_KERNEL);
+	if (!p8_i2c_occ)
+		return -ENOMEM;
+
+	p8_i2c_occ->client = client;
+	occ = &p8_i2c_occ->occ;
+	occ->bus_dev = &client->dev;
+	dev_set_drvdata(&client->dev, occ);
+
+	occ->poll_cmd_data = 0x10;		/* P8 OCC poll data */
+	occ->send_cmd = p8_i2c_occ_send_cmd;
+
+	return occ_setup(occ, "p8_occ");
+}
+
+static const struct of_device_id p8_i2c_occ_of_match[] = {
+	{ .compatible = "ibm,p8-occ-hwmon" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match);
+
+static struct i2c_driver p8_i2c_occ_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name = "occ-hwmon",
+		.of_match_table = p8_i2c_occ_of_match,
+	},
+	.probe = p8_i2c_occ_probe,
+};
+
+module_i2c_driver(p8_i2c_occ_driver);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("BMC P8 OCC hwmon driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
new file mode 100644
index 0000000..58c3bb2
--- /dev/null
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "common.h"
+
+struct p9_sbe_occ {
+	struct occ occ;
+	struct device *sbe;
+};
+
+#define to_p9_sbe_occ(x)	container_of((x), struct p9_sbe_occ, occ)
+
+static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd)
+{
+	return -EOPNOTSUPP;
+}
+
+static int p9_sbe_occ_probe(struct platform_device *pdev)
+{
+	struct occ *occ;
+	struct p9_sbe_occ *p9_sbe_occ = devm_kzalloc(&pdev->dev,
+						     sizeof(*p9_sbe_occ),
+						     GFP_KERNEL);
+	if (!p9_sbe_occ)
+		return -ENOMEM;
+
+	p9_sbe_occ->sbe = pdev->dev.parent;
+	occ = &p9_sbe_occ->occ;
+	occ->bus_dev = &pdev->dev;
+	platform_set_drvdata(pdev, occ);
+
+	occ->poll_cmd_data = 0x20;		/* P9 OCC poll data */
+	occ->send_cmd = p9_sbe_occ_send_cmd;
+
+	return occ_setup(occ, "p9_occ");
+}
+
+static const struct of_device_id p9_sbe_occ_of_match[] = {
+	{ .compatible = "ibm,p9-occ-hwmon" },
+	{ },
+};
+
+static struct platform_driver p9_sbe_occ_driver = {
+	.driver = {
+		.name = "occ-hwmon",
+		.of_match_table	= p9_sbe_occ_of_match,
+	},
+	.probe	= p9_sbe_occ_probe,
+};
+
+module_platform_driver(p9_sbe_occ_driver);
+
+MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>");
+MODULE_DESCRIPTION("BMC P9 OCC hwmon driver");
+MODULE_LICENSE("GPL");
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 06/12] hwmon (occ): Add command transport method for P8 and P9
@ 2017-11-20 23:53   ` Eddie James
  0 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

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 engine.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/p8_i2c.c | 185 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/hwmon/occ/p9_sbe.c |  95 ++++++++++++++++++++++-
 2 files changed, 278 insertions(+), 2 deletions(-)

diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
index 025471f..8032c0b 100644
--- a/drivers/hwmon/occ/p8_i2c.c
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -9,11 +9,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;
@@ -21,9 +39,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 int wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
+	struct p8_i2c_occ *p8_i2c_occ = to_p8_i2c_occ(occ);
+	struct i2c_client *client = p8_i2c_occ->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,
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index 58c3bb2..be3a469 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -9,21 +9,102 @@
 
 #include <linux/device.h>
 #include <linux/errno.h>
+#include <linux/fsi-occ.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/spinlock.h>
 
 #include "common.h"
 
 struct p9_sbe_occ {
 	struct occ occ;
 	struct device *sbe;
+
+	/*
+	 * Pointer to occ device client. We store this so that we can cancel
+	 * the client operations in remove() if necessary. We only need one
+	 * pointer since we do one OCC operation (open, write, read, close) at
+	 * a time (access to p9_sbe_occ_send_cmd is locked in the common code
+	 * with occ.lock).
+	 */
+	struct occ_client *client;
+
+	/*
+	 * This lock controls access to the client pointer and ensures atomic
+	 * open, close and NULL assignment. This prevents simultaneous opening
+	 * and closing of the client, or closing multiple times.
+	 */
+	spinlock_t lock;
 };
 
 #define to_p9_sbe_occ(x)	container_of((x), struct p9_sbe_occ, occ)
 
+static void p9_sbe_occ_close_client(struct p9_sbe_occ *occ)
+{
+	unsigned long flags;
+	struct occ_client *tmp_client;
+
+	spin_lock_irqsave(&occ->lock, flags);
+	tmp_client = occ->client;
+	occ->client = NULL;
+	occ_drv_release(tmp_client);
+	spin_unlock_irqrestore(&occ->lock, flags);
+}
+
 static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd)
 {
-	return -EOPNOTSUPP;
+	int rc;
+	unsigned long flags;
+	struct occ_response *resp = &occ->resp;
+	struct p9_sbe_occ *p9_sbe_occ = to_p9_sbe_occ(occ);
+
+	spin_lock_irqsave(&p9_sbe_occ->lock, flags);
+	if (p9_sbe_occ->sbe)
+		p9_sbe_occ->client = occ_drv_open(p9_sbe_occ->sbe, 0);
+	spin_unlock_irqrestore(&p9_sbe_occ->lock, flags);
+
+	if (!p9_sbe_occ->client)
+		return -ENODEV;
+
+	/* skip first byte (sequence number), OCC driver handles it */
+	rc = occ_drv_write(p9_sbe_occ->client, (const char *)&cmd[1], 7);
+	if (rc < 0)
+		goto err;
+
+	rc = occ_drv_read(p9_sbe_occ->client, (char *)resp, sizeof(*resp));
+	if (rc < 0)
+		goto err;
+
+	/* 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;
+	}
+
+err:
+	p9_sbe_occ_close_client(p9_sbe_occ);
+	return rc;
 }
 
 static int p9_sbe_occ_probe(struct platform_device *pdev)
@@ -46,6 +127,17 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 	return occ_setup(occ, "p9_occ");
 }
 
+static int p9_sbe_occ_remove(struct platform_device *pdev)
+{
+	struct occ *occ = platform_get_drvdata(pdev);
+	struct p9_sbe_occ *p9_sbe_occ = to_p9_sbe_occ(occ);
+
+	p9_sbe_occ->sbe = NULL;
+	p9_sbe_occ_close_client(p9_sbe_occ);
+
+	return 0;
+}
+
 static const struct of_device_id p9_sbe_occ_of_match[] = {
 	{ .compatible = "ibm,p9-occ-hwmon" },
 	{ },
@@ -57,6 +149,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 		.of_match_table	= p9_sbe_occ_of_match,
 	},
 	.probe	= p9_sbe_occ_probe,
+	.remove = p9_sbe_occ_remove,
 };
 
 module_platform_driver(p9_sbe_occ_driver);
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 06/12] hwmon (occ): Add command transport method for P8 and P9
@ 2017-11-20 23:53   ` Eddie James
  0 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ,
	jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	joel-U3u1mxZcP9KHXe+LvDLADg,
	eajames-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8, Edward A. James

From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

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 engine.

Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
---
 drivers/hwmon/occ/p8_i2c.c | 185 ++++++++++++++++++++++++++++++++++++++++++++-
 drivers/hwmon/occ/p9_sbe.c |  95 ++++++++++++++++++++++-
 2 files changed, 278 insertions(+), 2 deletions(-)

diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
index 025471f..8032c0b 100644
--- a/drivers/hwmon/occ/p8_i2c.c
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -9,11 +9,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;
@@ -21,9 +39,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 int wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
+	struct p8_i2c_occ *p8_i2c_occ = to_p8_i2c_occ(occ);
+	struct i2c_client *client = p8_i2c_occ->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,
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index 58c3bb2..be3a469 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -9,21 +9,102 @@
 
 #include <linux/device.h>
 #include <linux/errno.h>
+#include <linux/fsi-occ.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/spinlock.h>
 
 #include "common.h"
 
 struct p9_sbe_occ {
 	struct occ occ;
 	struct device *sbe;
+
+	/*
+	 * Pointer to occ device client. We store this so that we can cancel
+	 * the client operations in remove() if necessary. We only need one
+	 * pointer since we do one OCC operation (open, write, read, close) at
+	 * a time (access to p9_sbe_occ_send_cmd is locked in the common code
+	 * with occ.lock).
+	 */
+	struct occ_client *client;
+
+	/*
+	 * This lock controls access to the client pointer and ensures atomic
+	 * open, close and NULL assignment. This prevents simultaneous opening
+	 * and closing of the client, or closing multiple times.
+	 */
+	spinlock_t lock;
 };
 
 #define to_p9_sbe_occ(x)	container_of((x), struct p9_sbe_occ, occ)
 
+static void p9_sbe_occ_close_client(struct p9_sbe_occ *occ)
+{
+	unsigned long flags;
+	struct occ_client *tmp_client;
+
+	spin_lock_irqsave(&occ->lock, flags);
+	tmp_client = occ->client;
+	occ->client = NULL;
+	occ_drv_release(tmp_client);
+	spin_unlock_irqrestore(&occ->lock, flags);
+}
+
 static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd)
 {
-	return -EOPNOTSUPP;
+	int rc;
+	unsigned long flags;
+	struct occ_response *resp = &occ->resp;
+	struct p9_sbe_occ *p9_sbe_occ = to_p9_sbe_occ(occ);
+
+	spin_lock_irqsave(&p9_sbe_occ->lock, flags);
+	if (p9_sbe_occ->sbe)
+		p9_sbe_occ->client = occ_drv_open(p9_sbe_occ->sbe, 0);
+	spin_unlock_irqrestore(&p9_sbe_occ->lock, flags);
+
+	if (!p9_sbe_occ->client)
+		return -ENODEV;
+
+	/* skip first byte (sequence number), OCC driver handles it */
+	rc = occ_drv_write(p9_sbe_occ->client, (const char *)&cmd[1], 7);
+	if (rc < 0)
+		goto err;
+
+	rc = occ_drv_read(p9_sbe_occ->client, (char *)resp, sizeof(*resp));
+	if (rc < 0)
+		goto err;
+
+	/* 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;
+	}
+
+err:
+	p9_sbe_occ_close_client(p9_sbe_occ);
+	return rc;
 }
 
 static int p9_sbe_occ_probe(struct platform_device *pdev)
@@ -46,6 +127,17 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 	return occ_setup(occ, "p9_occ");
 }
 
+static int p9_sbe_occ_remove(struct platform_device *pdev)
+{
+	struct occ *occ = platform_get_drvdata(pdev);
+	struct p9_sbe_occ *p9_sbe_occ = to_p9_sbe_occ(occ);
+
+	p9_sbe_occ->sbe = NULL;
+	p9_sbe_occ_close_client(p9_sbe_occ);
+
+	return 0;
+}
+
 static const struct of_device_id p9_sbe_occ_of_match[] = {
 	{ .compatible = "ibm,p9-occ-hwmon" },
 	{ },
@@ -57,6 +149,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 		.of_match_table	= p9_sbe_occ_of_match,
 	},
 	.probe	= p9_sbe_occ_probe,
+	.remove = p9_sbe_occ_remove,
 };
 
 module_platform_driver(p9_sbe_occ_driver);
-- 
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 07/12] hwmon (occ): Parse OCC poll response
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (5 preceding siblings ...)
  2017-11-20 23:53   ` Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-20 23:53 ` [PATCH v3 08/12] hwmon (occ): Add sensor types and versions Eddie James
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

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.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/common.c | 50 ++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index 92f06ae..c55aec0 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -8,6 +8,7 @@
  */
 
 #include <linux/device.h>
+#include <linux/kernel.h>
 
 #include "common.h"
 
@@ -29,6 +30,53 @@ 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, 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;
+
+	for (i = 0; i < header->num_sensor_data_blocks; ++i) {
+		block = (struct occ_sensor_data_block *)((u8 *)block + 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;
+		}
+
+		/* 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;
+	}
+}
+
 int occ_setup(struct occ *occ, const char *name)
 {
 	int rc;
@@ -40,5 +88,7 @@ int occ_setup(struct occ *occ, const char *name)
 		return rc;
 	}
 
+	occ_parse_poll_response(occ);
+
 	return 0;
 }
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index 705dfe3..f52d45a 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -26,10 +26,64 @@ struct occ_response {
 	__be16 checksum;
 } __packed;
 
+struct occ_sensor_data_block_header {
+	u8 eye_catcher[4];
+	u8 reserved;
+	u8 sensor_format;
+	u8 sensor_length;
+	u8 num_sensors;
+} __packed;
+
+struct occ_sensor_data_block {
+	struct occ_sensor_data_block_header header;
+	u32 data;
+} __packed;
+
+struct occ_poll_response_header {
+	u8 status;
+	u8 ext_status;
+	u8 occs_present;
+	u8 config_data;
+	u8 occ_state;
+	u8 mode;
+	u8 ips_status;
+	u8 error_log_id;
+	__be32 error_log_start_address;
+	__be16 error_log_length;
+	u16 reserved;
+	u8 occ_code_level[16];
+	u8 eye_catcher[6];
+	u8 num_sensor_data_blocks;
+	u8 sensor_data_block_header_version;
+} __packed;
+
+struct occ_poll_response {
+	struct occ_poll_response_header header;
+	struct occ_sensor_data_block block;
+} __packed;
+
+struct occ_sensor {
+	u8 num_sensors;
+	u8 version;
+	void *data;	/* pointer to sensor data start within response */
+};
+
+/* OCC only provides one sensor data block of each type, but any number of
+ * sensors within that block.
+ */
+struct occ_sensors {
+	struct occ_sensor temp;
+	struct occ_sensor freq;
+	struct occ_sensor power;
+	struct occ_sensor caps;
+	struct occ_sensor extended;
+};
+
 struct occ {
 	struct device *bus_dev;
 
 	struct occ_response resp;
+	struct occ_sensors sensors;
 
 	u8 poll_cmd_data;		/* to perform OCC poll command */
 	int (*send_cmd)(struct occ *occ, u8 *cmd);
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 08/12] hwmon (occ): Add sensor types and versions
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (6 preceding siblings ...)
  2017-11-20 23:53 ` [PATCH v3 07/12] hwmon (occ): Parse OCC poll response Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-20 23:53 ` [PATCH v3 09/12] hwmon (occ): Add sensor attributes and register hwmon device Eddie James
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

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).

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/common.c | 648 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h |   5 +
 2 files changed, 653 insertions(+)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index c55aec0..7783019 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -8,10 +8,119 @@
  */
 
 #include <linux/device.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
 #include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <asm/unaligned.h>
 
 #include "common.h"
 
+#define OCC_UPDATE_FREQUENCY		msecs_to_jiffies(1000)
+
+#define OCC_TEMP_SENSOR_FAULT		0xFF
+
+#define OCC_FRU_TYPE_VRM		0x3
+
+/* 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_1 {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+} __packed;
+
+struct caps_sensor_2 {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+	u8 user_powerlimit_source;
+} __packed;
+
+struct caps_sensor_3 {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 hard_min_powercap;
+	u16 soft_min_powercap;
+	u16 user_powerlimit;
+	u8 user_powerlimit_source;
+} __packed;
+
+struct extended_sensor {
+	u8 name[4];
+	u8 flags;
+	u8 reserved;
+	u8 data[6];
+} __packed;
+
 static int occ_poll(struct occ *occ)
 {
 	u16 checksum = occ->poll_cmd_data + 1;
@@ -27,9 +136,545 @@ 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;
+	u16 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:
+		/* millidegrees */
+		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;
+
+		if (temp->fru_type != OCC_FRU_TYPE_VRM) {
+			/* sensor not ready */
+			if (val == 0)
+				return -EAGAIN;
+
+			val *= 1000;	/* millidegrees */
+		}
+		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;
+	u32 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->update_tag);
+		break;
+	case 2:
+		val = get_unaligned_be32(&power->accumulator);
+		break;
+	case 3:
+		/* microwatts */
+		val = get_unaligned_be16(&power->value) * 1000000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+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:
+		val = get_unaligned_be32(&power->sensor_id);
+		break;
+	case 1:
+		val = get_unaligned_be32(&power->update_tag);
+		break;
+	case 2:
+		val = get_unaligned_be64(&power->accumulator);
+		break;
+	case 3:
+		/* microwatts */
+		val = get_unaligned_be16(&power->value) * 1000000;
+		break;
+	case 4:
+		val = power->function_id;
+		break;
+	case 5:
+		val = power->apss_channel;
+		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:
+		val = get_unaligned_be32(&power->sensor_id);
+		break;
+	case 1:
+		return snprintf(buf, PAGE_SIZE - 1, "system\n");
+	case 2:
+		val = get_unaligned_be16(&power->system.update_time);
+		break;
+	case 3:
+		/* microwatts */
+		val = get_unaligned_be16(&power->system.value) * 1000000;
+		break;
+	case 4:
+		val = get_unaligned_be32(&power->system.update_tag);
+		break;
+	case 5:
+		val = get_unaligned_be64(&power->system.accumulator);
+		break;
+	case 6:
+		return snprintf(buf, PAGE_SIZE - 1, "proc\n");
+	case 7:
+		val = get_unaligned_be16(&power->proc.update_time);
+		break;
+	case 8:
+		/* microwatts */
+		val = get_unaligned_be16(&power->proc.value) * 1000000;
+		break;
+	case 9:
+		val = get_unaligned_be32(&power->proc.update_tag);
+		break;
+	case 10:
+		val = get_unaligned_be64(&power->proc.accumulator);
+		break;
+	case 11:
+		return snprintf(buf, PAGE_SIZE - 1, "vdd\n");
+	case 12:
+		/* microwatts */
+		val = get_unaligned_be16(&power->vdd.value) * 1000000;
+		break;
+	case 13:
+		val = get_unaligned_be32(&power->vdd.update_tag);
+		break;
+	case 14:
+		val = get_unaligned_be64(&power->vdd.accumulator);
+		break;
+	case 15:
+		return snprintf(buf, PAGE_SIZE - 1, "vdn\n");
+	case 16:
+		/* microwatts */
+		val = get_unaligned_be16(&power->vdn.value) * 1000000;
+		break;
+	case 17:
+		val = get_unaligned_be32(&power->vdn.update_tag);
+		break;
+	case 18:
+		val = get_unaligned_be64(&power->vdn.accumulator);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static ssize_t occ_show_caps_1(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u16 val = 0;
+	struct caps_sensor_1 *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_1 *)sensors->caps.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = get_unaligned_be16(&caps->curr_powercap);
+		break;
+	case 1:
+		val = get_unaligned_be16(&caps->curr_powerreading);
+		break;
+	case 2:
+		val = get_unaligned_be16(&caps->norm_powercap);
+		break;
+	case 3:
+		val = get_unaligned_be16(&caps->max_powercap);
+		break;
+	case 4:
+		val = get_unaligned_be16(&caps->min_powercap);
+		break;
+	case 5:
+		val = get_unaligned_be16(&caps->user_powerlimit);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t occ_show_caps_2(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u16 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:
+		val = get_unaligned_be16(&caps->curr_powercap);
+		break;
+	case 1:
+		val = get_unaligned_be16(&caps->curr_powerreading);
+		break;
+	case 2:
+		val = get_unaligned_be16(&caps->norm_powercap);
+		break;
+	case 3:
+		val = get_unaligned_be16(&caps->max_powercap);
+		break;
+	case 4:
+		val = get_unaligned_be16(&caps->min_powercap);
+		break;
+	case 5:
+		val = get_unaligned_be16(&caps->user_powerlimit);
+		break;
+	case 6:
+		val = caps->user_powerlimit_source;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t occ_show_caps_3(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	u16 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:
+		val = get_unaligned_be16(&caps->curr_powercap);
+		break;
+	case 1:
+		val = get_unaligned_be16(&caps->curr_powerreading);
+		break;
+	case 2:
+		val = get_unaligned_be16(&caps->norm_powercap);
+		break;
+	case 3:
+		val = get_unaligned_be16(&caps->max_powercap);
+		break;
+	case 4:
+		val = get_unaligned_be16(&caps->hard_min_powercap);
+		break;
+	case 5:
+		val = get_unaligned_be16(&caps->user_powerlimit);
+		break;
+	case 6:
+		val = caps->user_powerlimit_source;
+		break;
+	case 7:
+		val = get_unaligned_be16(&caps->soft_min_powercap);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\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;
+	struct occ *occ = dev_get_drvdata(dev);
+
+	rc = kstrtou16(buf, 0, &user_power_cap);
+	if (rc)
+		return rc;
+
+	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:
+		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)
 {
@@ -81,6 +726,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 < 0) {
 		dev_err(occ->bus_dev, "failed to get OCC poll response: %d\n",
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index f52d45a..3e4ca4d 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -10,6 +10,8 @@
 #ifndef OCC_COMMON_H
 #define OCC_COMMON_H
 
+#include <linux/mutex.h>
+
 struct device;
 
 #define OCC_RESP_DATA_BYTES		4089
@@ -87,6 +89,9 @@ struct occ {
 
 	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);
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 09/12] hwmon (occ): Add sensor attributes and register hwmon device
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (7 preceding siblings ...)
  2017-11-20 23:53 ` [PATCH v3 08/12] hwmon (occ): Add sensor types and versions Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-22 15:22   ` [v3, " Guenter Roeck
  2017-11-20 23:53   ` Eddie James
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

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

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/common.c | 450 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h |  15 ++
 2 files changed, 465 insertions(+)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index 7783019..337f286b 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -8,10 +8,12 @@
  */
 
 #include <linux/device.h>
+#include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/mutex.h>
+#include <linux/sysfs.h>
 #include <asm/unaligned.h>
 
 #include "common.h"
@@ -675,6 +677,437 @@ 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;
+
+	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 1:
+		num_attrs += (sensors->power.num_sensors * 4);
+		break;
+	case 2:
+		num_attrs += (sensors->power.num_sensors * 6);
+		show_power = occ_show_power_2;
+		break;
+	case 0xA0:
+		num_attrs += (sensors->power.num_sensors * 19);
+		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 * 6);
+		break;
+	case 2:
+		num_attrs += (sensors->caps.num_sensors * 7);
+		show_caps = occ_show_caps_2;
+		break;
+	case 3:
+		num_attrs += (sensors->caps.num_sensors * 8);
+		show_caps = occ_show_caps_3;
+		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; ) {
+			s = (i * 4) + 1;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_id", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 0, i);
+			attr++;
+
+			/* system power attributes */
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_label", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 1, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_update_time", 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++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_update_tag", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 4, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_accumulator", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 5, i);
+			attr++;
+
+			s++;
+
+			/* processor power attributes */
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_label", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 6, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_update_time", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 7, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_input", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 8, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_update_tag", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 9, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_accumulator", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 10, i);
+			attr++;
+
+			s++;
+
+			/* vdd power attributes */
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_label", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 11, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_input", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 12, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_update_tag", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 13, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_accumulator", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 14, i);
+			attr++;
+
+			s++;
+
+			/* vdn power attributes */
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_label", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 15, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_input", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 16, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_update_tag", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 17, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_accumulator", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 18, i);
+			attr++;
+		}
+	} 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_update_tag", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_power, NULL, 1, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "power%d_accumulator", 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->power.version > 1) {
+				snprintf(attr->name, sizeof(attr->name),
+					 "power%d_function_id", s);
+				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+							     show_power, NULL,
+							     4, i);
+				attr++;
+
+				snprintf(attr->name, sizeof(attr->name),
+					 "power%d_apss_channel", s);
+				attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+							     show_power, NULL,
+							     5, i);
+				attr++;
+			}
+		}
+	}
+
+	for (i = 0; i < sensors->caps.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_current", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_reading", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     1, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_norm", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     2, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_max", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL,
+					     3, i);
+		attr++;
+
+		if (sensors->caps.version > 2) {
+			snprintf(attr->name, sizeof(attr->name),
+				 "caps%d_min_hard", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_caps, NULL, 4, i);
+			attr++;
+
+			snprintf(attr->name, sizeof(attr->name),
+				 "caps%d_min_soft", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_caps, NULL, 7, i);
+			attr++;
+		} else {
+			snprintf(attr->name, sizeof(attr->name), "caps%d_min",
+				 s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_caps, NULL, 4, i);
+			attr++;
+		}
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_user", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps,
+					     occ_store_caps_user, 5, i);
+		attr++;
+
+		if (sensors->caps.version > 1) {
+			snprintf(attr->name, sizeof(attr->name),
+				 "caps%d_user_source", s);
+			attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+						     show_caps, NULL, 6, i);
+			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)
+		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)
 {
@@ -727,6 +1160,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);
@@ -738,5 +1172,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;
 }
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index 3e4ca4d..049c3b4 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -10,7 +10,9 @@
 #ifndef OCC_COMMON_H
 #define OCC_COMMON_H
 
+#include <linux/hwmon-sysfs.h>
 #include <linux/mutex.h>
+#include <linux/sysfs.h>
 
 struct device;
 
@@ -81,6 +83,14 @@ 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;
 
@@ -92,6 +102,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);
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 10/12] hwmon (occ): Add non-hwmon attributes
@ 2017-11-20 23:53   ` Eddie James
  0 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Create device attributes for additional OCC properties that do not
belong as hwmon sensors. These provide additional information as to the
state of the processor and system.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/common.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h |  1 +
 drivers/hwmon/occ/p8_i2c.c | 10 +++++
 drivers/hwmon/occ/p9_sbe.c |  2 +
 4 files changed, 106 insertions(+)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index 337f286b..53e3592 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -24,6 +24,14 @@
 
 #define OCC_FRU_TYPE_VRM		0x3
 
+/* OCC status bits */
+#define OCC_STAT_MASTER			0x80
+#define OCC_STAT_ACTIVE			0x01
+#define OCC_EXT_STAT_DVFS_OT		0x80
+#define OCC_EXT_STAT_DVFS_POWER		0x40
+#define OCC_EXT_STAT_MEM_THROTTLE	0x20
+#define OCC_EXT_STAT_QUICK_DROP		0x10
+
 /* OCC sensor type and version definitions */
 
 struct temp_sensor_1 {
@@ -1108,6 +1116,81 @@ static int occ_setup_sensor_attrs(struct occ *occ)
 	return 0;
 }
 
+static ssize_t occ_show_status(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	int val = 0;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_poll_response_header *header;
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	header = (struct occ_poll_response_header *)occ->resp.data;
+
+	switch (sattr->index) {
+	case 0:
+		val = (header->status & OCC_STAT_MASTER) ? 1 : 0;
+		break;
+	case 1:
+		val = (header->status & OCC_STAT_ACTIVE) ? 1 : 0;
+		break;
+	case 2:
+		val = (header->ext_status & OCC_EXT_STAT_DVFS_OT) ? 1 : 0;
+		break;
+	case 3:
+		val = (header->ext_status & OCC_EXT_STAT_DVFS_POWER) ? 1 : 0;
+		break;
+	case 4:
+		val = (header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) ? 1 : 0;
+		break;
+	case 5:
+		val = (header->ext_status & OCC_EXT_STAT_QUICK_DROP) ? 1 : 0;
+		break;
+	case 6:
+		val = header->occ_state;
+		break;
+	case 7:
+		if (header->status & OCC_STAT_MASTER)
+			val = hweight8(header->occs_present);
+		else
+			val = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_show_status, NULL, 0);
+static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_show_status, NULL, 1);
+static SENSOR_DEVICE_ATTR(occ_dvfs_ot, 0444, occ_show_status, NULL, 2);
+static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_show_status, NULL, 3);
+static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_show_status, NULL, 4);
+static SENSOR_DEVICE_ATTR(occ_quick_drop, 0444, occ_show_status, NULL, 5);
+static SENSOR_DEVICE_ATTR(occ_status, 0444, occ_show_status, NULL, 6);
+static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_show_status, NULL, 7);
+
+static struct attribute *occ_attributes[] = {
+	&sensor_dev_attr_occ_master.dev_attr.attr,
+	&sensor_dev_attr_occ_active.dev_attr.attr,
+	&sensor_dev_attr_occ_dvfs_ot.dev_attr.attr,
+	&sensor_dev_attr_occ_dvfs_power.dev_attr.attr,
+	&sensor_dev_attr_occ_mem_throttle.dev_attr.attr,
+	&sensor_dev_attr_occ_quick_drop.dev_attr.attr,
+	&sensor_dev_attr_occ_status.dev_attr.attr,
+	&sensor_dev_attr_occs_present.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group occ_attr_group = {
+	.attrs = occ_attributes,
+};
+
 /* 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)
 {
@@ -1188,5 +1271,15 @@ int occ_setup(struct occ *occ, const char *name)
 		return rc;
 	}
 
+	rc = sysfs_create_group(&occ->bus_dev->kobj, &occ_attr_group);
+	if (rc)
+		dev_warn(occ->bus_dev, "failed to create status attrs: %d\n",
+			 rc);
+
 	return 0;
 }
+
+void occ_shutdown(struct occ *occ)
+{
+	sysfs_remove_group(&occ->bus_dev->kobj, &occ_attr_group);
+}
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index 049c3b4..dc9e06d 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -110,5 +110,6 @@ struct occ {
 };
 
 int occ_setup(struct occ *occ, const char *name);
+void occ_shutdown(struct occ *occ);
 
 #endif /* OCC_COMMON_H */
diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
index 8032c0b..d719632 100644
--- a/drivers/hwmon/occ/p8_i2c.c
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -230,6 +230,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" },
 	{}
@@ -243,6 +252,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);
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index be3a469..7dbe4d5 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -135,6 +135,8 @@ static int p9_sbe_occ_remove(struct platform_device *pdev)
 	p9_sbe_occ->sbe = NULL;
 	p9_sbe_occ_close_client(p9_sbe_occ);
 
+	occ_shutdown(occ);
+
 	return 0;
 }
 
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 10/12] hwmon (occ): Add non-hwmon attributes
@ 2017-11-20 23:53   ` Eddie James
  0 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ,
	jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	joel-U3u1mxZcP9KHXe+LvDLADg,
	eajames-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8, Edward A. James

From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>

Create device attributes for additional OCC properties that do not
belong as hwmon sensors. These provide additional information as to the
state of the processor and system.

Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
---
 drivers/hwmon/occ/common.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/common.h |  1 +
 drivers/hwmon/occ/p8_i2c.c | 10 +++++
 drivers/hwmon/occ/p9_sbe.c |  2 +
 4 files changed, 106 insertions(+)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index 337f286b..53e3592 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -24,6 +24,14 @@
 
 #define OCC_FRU_TYPE_VRM		0x3
 
+/* OCC status bits */
+#define OCC_STAT_MASTER			0x80
+#define OCC_STAT_ACTIVE			0x01
+#define OCC_EXT_STAT_DVFS_OT		0x80
+#define OCC_EXT_STAT_DVFS_POWER		0x40
+#define OCC_EXT_STAT_MEM_THROTTLE	0x20
+#define OCC_EXT_STAT_QUICK_DROP		0x10
+
 /* OCC sensor type and version definitions */
 
 struct temp_sensor_1 {
@@ -1108,6 +1116,81 @@ static int occ_setup_sensor_attrs(struct occ *occ)
 	return 0;
 }
 
+static ssize_t occ_show_status(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int rc;
+	int val = 0;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_poll_response_header *header;
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	header = (struct occ_poll_response_header *)occ->resp.data;
+
+	switch (sattr->index) {
+	case 0:
+		val = (header->status & OCC_STAT_MASTER) ? 1 : 0;
+		break;
+	case 1:
+		val = (header->status & OCC_STAT_ACTIVE) ? 1 : 0;
+		break;
+	case 2:
+		val = (header->ext_status & OCC_EXT_STAT_DVFS_OT) ? 1 : 0;
+		break;
+	case 3:
+		val = (header->ext_status & OCC_EXT_STAT_DVFS_POWER) ? 1 : 0;
+		break;
+	case 4:
+		val = (header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) ? 1 : 0;
+		break;
+	case 5:
+		val = (header->ext_status & OCC_EXT_STAT_QUICK_DROP) ? 1 : 0;
+		break;
+	case 6:
+		val = header->occ_state;
+		break;
+	case 7:
+		if (header->status & OCC_STAT_MASTER)
+			val = hweight8(header->occs_present);
+		else
+			val = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_show_status, NULL, 0);
+static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_show_status, NULL, 1);
+static SENSOR_DEVICE_ATTR(occ_dvfs_ot, 0444, occ_show_status, NULL, 2);
+static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_show_status, NULL, 3);
+static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_show_status, NULL, 4);
+static SENSOR_DEVICE_ATTR(occ_quick_drop, 0444, occ_show_status, NULL, 5);
+static SENSOR_DEVICE_ATTR(occ_status, 0444, occ_show_status, NULL, 6);
+static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_show_status, NULL, 7);
+
+static struct attribute *occ_attributes[] = {
+	&sensor_dev_attr_occ_master.dev_attr.attr,
+	&sensor_dev_attr_occ_active.dev_attr.attr,
+	&sensor_dev_attr_occ_dvfs_ot.dev_attr.attr,
+	&sensor_dev_attr_occ_dvfs_power.dev_attr.attr,
+	&sensor_dev_attr_occ_mem_throttle.dev_attr.attr,
+	&sensor_dev_attr_occ_quick_drop.dev_attr.attr,
+	&sensor_dev_attr_occ_status.dev_attr.attr,
+	&sensor_dev_attr_occs_present.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group occ_attr_group = {
+	.attrs = occ_attributes,
+};
+
 /* 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)
 {
@@ -1188,5 +1271,15 @@ int occ_setup(struct occ *occ, const char *name)
 		return rc;
 	}
 
+	rc = sysfs_create_group(&occ->bus_dev->kobj, &occ_attr_group);
+	if (rc)
+		dev_warn(occ->bus_dev, "failed to create status attrs: %d\n",
+			 rc);
+
 	return 0;
 }
+
+void occ_shutdown(struct occ *occ)
+{
+	sysfs_remove_group(&occ->bus_dev->kobj, &occ_attr_group);
+}
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index 049c3b4..dc9e06d 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -110,5 +110,6 @@ struct occ {
 };
 
 int occ_setup(struct occ *occ, const char *name);
+void occ_shutdown(struct occ *occ);
 
 #endif /* OCC_COMMON_H */
diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
index 8032c0b..d719632 100644
--- a/drivers/hwmon/occ/p8_i2c.c
+++ b/drivers/hwmon/occ/p8_i2c.c
@@ -230,6 +230,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" },
 	{}
@@ -243,6 +252,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);
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index be3a469..7dbe4d5 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -135,6 +135,8 @@ static int p9_sbe_occ_remove(struct platform_device *pdev)
 	p9_sbe_occ->sbe = NULL;
 	p9_sbe_occ_close_client(p9_sbe_occ);
 
+	occ_shutdown(occ);
+
 	return 0;
 }
 
-- 
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 11/12] hwmon (occ): Add error handling
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (9 preceding siblings ...)
  2017-11-20 23:53   ` Eddie James
@ 2017-11-20 23:53 ` Eddie James
  2017-11-20 23:53 ` [PATCH v3 12/12] hwmon (occ): Add sysfs notification for errors and throttling Eddie James
  11 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

Add logic to detect a number of error scenarios on the OCC. Export any
errors through an additional non-hwmon device attribute. The error
counting and state verification are required by the OCC hardware
specification.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/common.c | 55 +++++++++++++++++++++++++++++++++++++++++++++-
 drivers/hwmon/occ/common.h |  4 ++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index 53e3592..7a0606d 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -18,6 +18,11 @@
 
 #include "common.h"
 
+#define OCC_ERROR_COUNT_THRESHOLD	2	/* OCC HW defined */
+
+#define OCC_STATE_SAFE			4
+#define OCC_SAFE_TIMEOUT		msecs_to_jiffies(60000) /* 1 min */
+
 #define OCC_UPDATE_FREQUENCY		msecs_to_jiffies(1000)
 
 #define OCC_TEMP_SENSOR_FAULT		0xFF
@@ -131,10 +136,22 @@ struct extended_sensor {
 	u8 data[6];
 } __packed;
 
+static ssize_t occ_show_error(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct occ *occ = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%d\n", occ->error);
+}
+
+static DEVICE_ATTR(occ_error, 0444, occ_show_error, NULL);
+
 static int occ_poll(struct occ *occ)
 {
+	struct occ_poll_response_header *header;
 	u16 checksum = occ->poll_cmd_data + 1;
 	u8 cmd[8];
+	int rc;
 
 	/* big endian */
 	cmd[0] = 0;			/* sequence number */
@@ -147,7 +164,33 @@ static int occ_poll(struct occ *occ)
 	cmd[7] = 0;
 
 	/* mutex should already be locked if necessary */
-	return occ->send_cmd(occ, cmd);
+	rc = occ->send_cmd(occ, cmd);
+	if (rc) {
+		if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD)
+			occ->error = rc;
+
+		return rc;
+	}
+
+	/* clear error since communication was successful */
+	occ->error_count = 0;
+	occ->error = 0;
+
+	/* check for safe state */
+	header = (struct occ_poll_response_header *)occ->resp.data;
+	if (header->occ_state == OCC_STATE_SAFE) {
+		if (occ->last_safe) {
+			if (time_after(jiffies,
+				       occ->last_safe + OCC_SAFE_TIMEOUT))
+				occ->error = -EHOSTDOWN;
+		} else {
+			occ->last_safe = jiffies;
+		}
+	} else {
+		occ->last_safe = 0;
+	}
+
+	return 0;
 }
 
 static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
@@ -176,6 +219,15 @@ static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
 
 	mutex_unlock(&occ->lock);
 
+	if (rc) {
+		if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD)
+			occ->error = rc;
+	} else {
+		/* successful communication so clear the error */
+		occ->error_count = 0;
+		occ->error = 0;
+	}
+
 	return rc;
 }
 
@@ -1184,6 +1236,7 @@ static ssize_t occ_show_status(struct device *dev,
 	&sensor_dev_attr_occ_quick_drop.dev_attr.attr,
 	&sensor_dev_attr_occ_status.dev_attr.attr,
 	&sensor_dev_attr_occs_present.dev_attr.attr,
+	&dev_attr_occ_error.attr,
 	NULL
 };
 
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index dc9e06d..cef2174 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -107,6 +107,10 @@ struct occ {
 	struct occ_attribute *attrs;
 	struct attribute_group group;
 	const struct attribute_group *groups[2];
+
+	int error;
+	unsigned int error_count;	/* number of errors observed */
+	unsigned long last_safe;	/* time OCC entered safe state */
 };
 
 int occ_setup(struct occ *occ, const char *name);
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v3 12/12] hwmon (occ): Add sysfs notification for errors and throttling
  2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
                   ` (10 preceding siblings ...)
  2017-11-20 23:53 ` [PATCH v3 11/12] hwmon (occ): Add error handling Eddie James
@ 2017-11-20 23:53 ` Eddie James
  11 siblings, 0 replies; 22+ messages in thread
From: Eddie James @ 2017-11-20 23:53 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-hwmon, devicetree, linux-doc, linux, jdelvare, corbet,
	mark.rutland, robh+dt, joel, eajames, Edward A. James

From: "Edward A. James" <eajames@us.ibm.com>

In order to aid application usage of the error, throttling, and
presence count properties, use sysfs_notify to notify users of change on
these attributes.

Signed-off-by: Edward A. James <eajames@us.ibm.com>
---
 drivers/hwmon/occ/common.c | 53 ++++++++++++++++++++++++++++++++++++++++++++--
 drivers/hwmon/occ/common.h |  5 +++++
 2 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index 7a0606d..7c97a4c 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -146,6 +146,8 @@ static ssize_t occ_show_error(struct device *dev,
 
 static DEVICE_ATTR(occ_error, 0444, occ_show_error, NULL);
 
+static void occ_sysfs_notify(struct occ *occ);
+
 static int occ_poll(struct occ *occ)
 {
 	struct occ_poll_response_header *header;
@@ -169,7 +171,7 @@ static int occ_poll(struct occ *occ)
 		if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD)
 			occ->error = rc;
 
-		return rc;
+		goto done;
 	}
 
 	/* clear error since communication was successful */
@@ -190,7 +192,9 @@ static int occ_poll(struct occ *occ)
 		occ->last_safe = 0;
 	}
 
-	return 0;
+done:
+	occ_sysfs_notify(occ);
+	return rc;
 }
 
 static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
@@ -1244,6 +1248,51 @@ static ssize_t occ_show_status(struct device *dev,
 	.attrs = occ_attributes,
 };
 
+static void occ_sysfs_notify(struct occ *occ)
+{
+	const char *name;
+	struct occ_poll_response_header *header =
+		(struct occ_poll_response_header *)occ->resp.data;
+
+	/* sysfs attributes aren't loaded yet; don't proceed */
+	if (!occ->hwmon)
+		goto done;
+
+	if (header->occs_present != occ->previous_occs_present &&
+	    (header->status & OCC_STAT_MASTER)) {
+		name = sensor_dev_attr_occs_present.dev_attr.attr.name;
+		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
+	}
+
+	if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) !=
+	    (occ->previous_ext_status & OCC_EXT_STAT_DVFS_OT)) {
+		name = sensor_dev_attr_occ_dvfs_ot.dev_attr.attr.name;
+		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
+	}
+
+	if ((header->ext_status & OCC_EXT_STAT_DVFS_POWER) !=
+	    (occ->previous_ext_status & OCC_EXT_STAT_DVFS_POWER)) {
+		name = sensor_dev_attr_occ_dvfs_power.dev_attr.attr.name;
+		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
+	}
+
+	if ((header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) !=
+	    (occ->previous_ext_status & OCC_EXT_STAT_MEM_THROTTLE)) {
+		name = sensor_dev_attr_occ_mem_throttle.dev_attr.attr.name;
+		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
+	}
+
+	if (occ->error && occ->error != occ->previous_error) {
+		name = dev_attr_occ_error.attr.name;
+		sysfs_notify(&occ->bus_dev->kobj, NULL, name);
+	}
+
+done:
+	occ->previous_error = occ->error;
+	occ->previous_ext_status = header->ext_status;
+	occ->previous_occs_present = header->occs_present;
+}
+
 /* 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)
 {
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index cef2174..ffc809f 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -111,6 +111,11 @@ struct occ {
 	int error;
 	unsigned int error_count;	/* number of errors observed */
 	unsigned long last_safe;	/* time OCC entered safe state */
+
+	/* store previous poll state to compare; notify sysfs on change */
+	int previous_error;
+	u8 previous_ext_status;
+	u8 previous_occs_present;
 };
 
 int occ_setup(struct occ *occ, const char *name);
-- 
1.8.3.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 03/12] dt-bindings: i2c: Add P8 OCC hwmon device documentation
  2017-11-20 23:53 ` [PATCH v3 03/12] dt-bindings: i2c: Add P8 OCC hwmon device documentation Eddie James
@ 2017-11-21 18:34   ` Rob Herring
  0 siblings, 0 replies; 22+ messages in thread
From: Rob Herring @ 2017-11-21 18:34 UTC (permalink / raw)
  To: Eddie James
  Cc: linux-kernel, linux-hwmon, devicetree, linux-doc, linux,
	jdelvare, corbet, mark.rutland, joel, Edward A. James

On Mon, Nov 20, 2017 at 05:53:32PM -0600, Eddie James wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
> 
> Document the bindings for I2C-based OCC hwmon device.
> 
> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> ---
>  .../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

Acked-by: Rob Herring <robh@kernel.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 04/12] dt-bindings: fsi: Add P9 OCC hwmon device documentation
@ 2017-11-21 18:36     ` Rob Herring
  0 siblings, 0 replies; 22+ messages in thread
From: Rob Herring @ 2017-11-21 18:36 UTC (permalink / raw)
  To: Eddie James
  Cc: linux-kernel, linux-hwmon, devicetree, linux-doc, linux,
	jdelvare, corbet, mark.rutland, joel, Edward A. James

On Mon, Nov 20, 2017 at 05:53:33PM -0600, Eddie James wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
> 
> Document the bindings for the FSI-based OCC hwmon device.
> 
> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> ---
>  .../devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt         | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
> 
> diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
> new file mode 100644
> index 0000000..086b1eb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
> @@ -0,0 +1,16 @@
> +Device-tree bindings for FSI-based On-Chip Controller hwmon device
> +------------------------------------------------------------------
> +
> +Required properties:
> + - compatible = "ibm,p9-occ-hwmon";
> +
> +Examples:
> +
> +    occ@1 {
> +        compatible = "ibm,p9-occ";
> +        reg = <1>;
> +
> +        occ-hwmon@1 {

Missing reg property or need to drop the unit-address.

> +            compatible = "ibm,p9-occ-hmwon";
> +        }

Missing ;

> +    };
> -- 
> 1.8.3.1
> 

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v3 04/12] dt-bindings: fsi: Add P9 OCC hwmon device documentation
@ 2017-11-21 18:36     ` Rob Herring
  0 siblings, 0 replies; 22+ messages in thread
From: Rob Herring @ 2017-11-21 18:36 UTC (permalink / raw)
  To: Eddie James
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, linux-0h96xk9xTtrk1uMJSBkQmQ,
	jdelvare-IBi9RG/b67k, corbet-T1hC0tSOHrs,
	mark.rutland-5wv7dgnIgG8, joel-U3u1mxZcP9KHXe+LvDLADg,
	Edward A. James

On Mon, Nov 20, 2017 at 05:53:33PM -0600, Eddie James wrote:
> From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
> 
> Document the bindings for the FSI-based OCC hwmon device.
> 
> Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
> ---
>  .../devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt         | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
> 
> diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
> new file mode 100644
> index 0000000..086b1eb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ-hwmon.txt
> @@ -0,0 +1,16 @@
> +Device-tree bindings for FSI-based On-Chip Controller hwmon device
> +------------------------------------------------------------------
> +
> +Required properties:
> + - compatible = "ibm,p9-occ-hwmon";
> +
> +Examples:
> +
> +    occ@1 {
> +        compatible = "ibm,p9-occ";
> +        reg = <1>;
> +
> +        occ-hwmon@1 {

Missing reg property or need to drop the unit-address.

> +            compatible = "ibm,p9-occ-hmwon";
> +        }

Missing ;

> +    };
> -- 
> 1.8.3.1
> 
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [v3, 02/12] Documentation: ABI: Add occ-hwmon driver sysfs documentation
  2017-11-20 23:53 ` [PATCH v3 02/12] Documentation: ABI: Add occ-hwmon driver sysfs documentation Eddie James
@ 2017-11-22 15:15   ` Guenter Roeck
  0 siblings, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2017-11-22 15:15 UTC (permalink / raw)
  To: eajames
  Cc: linux-kernel, linux-hwmon, devicetree, linux-doc, jdelvare,
	corbet, mark.rutland, robh+dt, joel, Edward A. James

On Mon, Nov 20, 2017 at 05:53:31PM -0600, eajames@linux.vnet.ibm.com wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
> 
> Detail the sysfs attributes provided by the occ-hwmon driver.
> 

This describes my problem with this driver: All the sysfs attributes
described here are not really hardware monitoring related. A later patch
describes this in more detail:

"The OCC can provide the raw sensor data as well as perform thermal
 and power management on the system."

Is it possible to extract the non-hwmon functionality (maybe into an
mfd driver) and limit the hwmon part to just hardware monitoring ?

Guenter


> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> ---
>  Documentation/ABI/testing/sysfs-driver-occ-hwmon | 85 ++++++++++++++++++++++++
>  1 file changed, 85 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-driver-occ-hwmon
> 
> diff --git a/Documentation/ABI/testing/sysfs-driver-occ-hwmon b/Documentation/ABI/testing/sysfs-driver-occ-hwmon
> new file mode 100644
> index 0000000..8873cc3
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-occ-hwmon
> @@ -0,0 +1,85 @@
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_active
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates (with a "1" or a "0",
> +		respectively) whether or not this OCC is in the "active" state.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_dvfs_ot
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates (with a "1" or a "0",
> +		respectively) whether or not this OCC has limited the processor
> +		frequency due to over-temperature.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_dvfs_power
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates (with a "1" or a "0",
> +		respectively) whether or not this OCC has limited the processor
> +		frequency due to power usage.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_error
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates any error condition
> +		observed by the OCC or detected by the driver. Reading the
> +		attribute will return an integer. A negative integer indicates
> +		either an error response from the OCC or bus error or other
> +		error condition detected by the driver. A "0" indicates no
> +		error.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_master
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates (with a "1" or a "0",
> +		respectively) whether or not this OCC is the "master" OCC.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_mem_throttle
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates (with a "1" or a "0",
> +		respectively) whether or not the OCC has throttled memory due
> +		to over-temperature.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occs_present
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates the number of OCCs present
> +		on the system.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_quick_drop
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates (with a "1" or a "0",
> +		respectively) whether or not this OCC has asserted the "quick
> +		power drop" signal.
> +
> +What:		/sys/bus/platform/drivers/occ-hwmon/<dev>/occ_status
> +Date:		November 2017
> +KernelVersion:	4.14
> +Contact:	eajames@us.ibm.com
> +Description:
> +		A read-only attribute that indicates the current OCC state. The
> +		value of the attribute will be one of the following states:
> +		0: Reserved
> +		1: Standby
> +		2: Observation
> +		3: Active
> +		4: Safe
> +		5: Characterization

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [v3, 09/12] hwmon (occ): Add sensor attributes and register hwmon device
  2017-11-20 23:53 ` [PATCH v3 09/12] hwmon (occ): Add sensor attributes and register hwmon device Eddie James
@ 2017-11-22 15:22   ` Guenter Roeck
  0 siblings, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2017-11-22 15:22 UTC (permalink / raw)
  To: eajames
  Cc: linux-kernel, linux-hwmon, devicetree, linux-doc, jdelvare,
	corbet, mark.rutland, robh+dt, joel, Edward A. James

On Mon, Nov 20, 2017 at 05:53:38PM -0600, eajames@linux.vnet.ibm.com wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
> 
> Setup the sensor attributes for every OCC sensor found by the first poll
> response. Register the attributes with hwmon. Add hwmon documentation
> for the driver.
> 
> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> ---
>  drivers/hwmon/occ/common.c | 450 +++++++++++++++++++++++++++++++++++++++++++++
>  drivers/hwmon/occ/common.h |  15 ++

I seem to be missing the documentation.

Documentation/hwmon/submitting-patches:

* Document the driver in Documentation/hwmon/<driver_name>.

* Do not create non-standard attributes unless really needed. If you have to use
  non-standard attributes, or you believe you do, discuss it on the mailing list
  first. Either case, provide a detailed explanation why you need the
  non-standard attribute(s).
  Standard attributes are specified in Documentation/hwmon/sysfs-interface.

I really would expect a detailed explanation and, even more so,
documentation for all the non-standard attributes you are adding.

power%d_update_time
power%d_update_tag
power%d_accumulator

really need to be documented, you really need to explain the need for those
attributes. To me they don't mean anything.

Note that you'd probably be much better off using
devm_register_hwmon_with_info() nowadays, but i'll leave that for you to
decide.

Guenter

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [v3,10/12] hwmon (occ): Add non-hwmon attributes
@ 2017-11-22 15:23     ` Guenter Roeck
  0 siblings, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2017-11-22 15:23 UTC (permalink / raw)
  To: eajames
  Cc: linux-kernel, linux-hwmon, devicetree, linux-doc, jdelvare,
	corbet, mark.rutland, robh+dt, joel, Edward A. James

On Mon, Nov 20, 2017 at 05:53:39PM -0600, eajames@linux.vnet.ibm.com wrote:
> From: "Edward A. James" <eajames@us.ibm.com>
> 
> Create device attributes for additional OCC properties that do not
> belong as hwmon sensors. These provide additional information as to the
> state of the processor and system.
> 
... and those attributes should really not be part of the hwmon driver.

> Signed-off-by: Edward A. James <eajames@us.ibm.com>
> ---
>  drivers/hwmon/occ/common.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/hwmon/occ/common.h |  1 +
>  drivers/hwmon/occ/p8_i2c.c | 10 +++++
>  drivers/hwmon/occ/p9_sbe.c |  2 +
>  4 files changed, 106 insertions(+)
> 
> diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
> index 337f286b..53e3592 100644
> --- a/drivers/hwmon/occ/common.c
> +++ b/drivers/hwmon/occ/common.c
> @@ -24,6 +24,14 @@
>  
>  #define OCC_FRU_TYPE_VRM		0x3
>  
> +/* OCC status bits */
> +#define OCC_STAT_MASTER			0x80
> +#define OCC_STAT_ACTIVE			0x01
> +#define OCC_EXT_STAT_DVFS_OT		0x80
> +#define OCC_EXT_STAT_DVFS_POWER		0x40
> +#define OCC_EXT_STAT_MEM_THROTTLE	0x20
> +#define OCC_EXT_STAT_QUICK_DROP		0x10
> +
>  /* OCC sensor type and version definitions */
>  
>  struct temp_sensor_1 {
> @@ -1108,6 +1116,81 @@ static int occ_setup_sensor_attrs(struct occ *occ)
>  	return 0;
>  }
>  
> +static ssize_t occ_show_status(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	int rc;
> +	int val = 0;
> +	struct occ *occ = dev_get_drvdata(dev);
> +	struct occ_poll_response_header *header;
> +	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
> +
> +	rc = occ_update_response(occ);
> +	if (rc)
> +		return rc;
> +
> +	header = (struct occ_poll_response_header *)occ->resp.data;
> +
> +	switch (sattr->index) {
> +	case 0:
> +		val = (header->status & OCC_STAT_MASTER) ? 1 : 0;
> +		break;
> +	case 1:
> +		val = (header->status & OCC_STAT_ACTIVE) ? 1 : 0;
> +		break;
> +	case 2:
> +		val = (header->ext_status & OCC_EXT_STAT_DVFS_OT) ? 1 : 0;
> +		break;
> +	case 3:
> +		val = (header->ext_status & OCC_EXT_STAT_DVFS_POWER) ? 1 : 0;
> +		break;
> +	case 4:
> +		val = (header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) ? 1 : 0;
> +		break;
> +	case 5:
> +		val = (header->ext_status & OCC_EXT_STAT_QUICK_DROP) ? 1 : 0;
> +		break;
> +	case 6:
> +		val = header->occ_state;
> +		break;
> +	case 7:
> +		if (header->status & OCC_STAT_MASTER)
> +			val = hweight8(header->occs_present);
> +		else
> +			val = 1;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
> +}
> +
> +static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_show_status, NULL, 0);
> +static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_show_status, NULL, 1);
> +static SENSOR_DEVICE_ATTR(occ_dvfs_ot, 0444, occ_show_status, NULL, 2);
> +static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_show_status, NULL, 3);
> +static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_show_status, NULL, 4);
> +static SENSOR_DEVICE_ATTR(occ_quick_drop, 0444, occ_show_status, NULL, 5);
> +static SENSOR_DEVICE_ATTR(occ_status, 0444, occ_show_status, NULL, 6);
> +static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_show_status, NULL, 7);
> +
> +static struct attribute *occ_attributes[] = {
> +	&sensor_dev_attr_occ_master.dev_attr.attr,
> +	&sensor_dev_attr_occ_active.dev_attr.attr,
> +	&sensor_dev_attr_occ_dvfs_ot.dev_attr.attr,
> +	&sensor_dev_attr_occ_dvfs_power.dev_attr.attr,
> +	&sensor_dev_attr_occ_mem_throttle.dev_attr.attr,
> +	&sensor_dev_attr_occ_quick_drop.dev_attr.attr,
> +	&sensor_dev_attr_occ_status.dev_attr.attr,
> +	&sensor_dev_attr_occs_present.dev_attr.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group occ_attr_group = {
> +	.attrs = occ_attributes,
> +};
> +
>  /* 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)
>  {
> @@ -1188,5 +1271,15 @@ int occ_setup(struct occ *occ, const char *name)
>  		return rc;
>  	}
>  
> +	rc = sysfs_create_group(&occ->bus_dev->kobj, &occ_attr_group);
> +	if (rc)
> +		dev_warn(occ->bus_dev, "failed to create status attrs: %d\n",
> +			 rc);
> +
>  	return 0;
>  }
> +
> +void occ_shutdown(struct occ *occ)
> +{
> +	sysfs_remove_group(&occ->bus_dev->kobj, &occ_attr_group);
> +}
> diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
> index 049c3b4..dc9e06d 100644
> --- a/drivers/hwmon/occ/common.h
> +++ b/drivers/hwmon/occ/common.h
> @@ -110,5 +110,6 @@ struct occ {
>  };
>  
>  int occ_setup(struct occ *occ, const char *name);
> +void occ_shutdown(struct occ *occ);
>  
>  #endif /* OCC_COMMON_H */
> diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
> index 8032c0b..d719632 100644
> --- a/drivers/hwmon/occ/p8_i2c.c
> +++ b/drivers/hwmon/occ/p8_i2c.c
> @@ -230,6 +230,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" },
>  	{}
> @@ -243,6 +252,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);
> diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
> index be3a469..7dbe4d5 100644
> --- a/drivers/hwmon/occ/p9_sbe.c
> +++ b/drivers/hwmon/occ/p9_sbe.c
> @@ -135,6 +135,8 @@ static int p9_sbe_occ_remove(struct platform_device *pdev)
>  	p9_sbe_occ->sbe = NULL;
>  	p9_sbe_occ_close_client(p9_sbe_occ);
>  
> +	occ_shutdown(occ);
> +
>  	return 0;
>  }
>  

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [v3,10/12] hwmon (occ): Add non-hwmon attributes
@ 2017-11-22 15:23     ` Guenter Roeck
  0 siblings, 0 replies; 22+ messages in thread
From: Guenter Roeck @ 2017-11-22 15:23 UTC (permalink / raw)
  To: eajames-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-hwmon-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, jdelvare-IBi9RG/b67k,
	corbet-T1hC0tSOHrs, mark.rutland-5wv7dgnIgG8,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, joel-U3u1mxZcP9KHXe+LvDLADg,
	Edward A. James

On Mon, Nov 20, 2017 at 05:53:39PM -0600, eajames-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org wrote:
> From: "Edward A. James" <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
> 
> Create device attributes for additional OCC properties that do not
> belong as hwmon sensors. These provide additional information as to the
> state of the processor and system.
> 
... and those attributes should really not be part of the hwmon driver.

> Signed-off-by: Edward A. James <eajames-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
> ---
>  drivers/hwmon/occ/common.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++
>  drivers/hwmon/occ/common.h |  1 +
>  drivers/hwmon/occ/p8_i2c.c | 10 +++++
>  drivers/hwmon/occ/p9_sbe.c |  2 +
>  4 files changed, 106 insertions(+)
> 
> diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
> index 337f286b..53e3592 100644
> --- a/drivers/hwmon/occ/common.c
> +++ b/drivers/hwmon/occ/common.c
> @@ -24,6 +24,14 @@
>  
>  #define OCC_FRU_TYPE_VRM		0x3
>  
> +/* OCC status bits */
> +#define OCC_STAT_MASTER			0x80
> +#define OCC_STAT_ACTIVE			0x01
> +#define OCC_EXT_STAT_DVFS_OT		0x80
> +#define OCC_EXT_STAT_DVFS_POWER		0x40
> +#define OCC_EXT_STAT_MEM_THROTTLE	0x20
> +#define OCC_EXT_STAT_QUICK_DROP		0x10
> +
>  /* OCC sensor type and version definitions */
>  
>  struct temp_sensor_1 {
> @@ -1108,6 +1116,81 @@ static int occ_setup_sensor_attrs(struct occ *occ)
>  	return 0;
>  }
>  
> +static ssize_t occ_show_status(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	int rc;
> +	int val = 0;
> +	struct occ *occ = dev_get_drvdata(dev);
> +	struct occ_poll_response_header *header;
> +	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
> +
> +	rc = occ_update_response(occ);
> +	if (rc)
> +		return rc;
> +
> +	header = (struct occ_poll_response_header *)occ->resp.data;
> +
> +	switch (sattr->index) {
> +	case 0:
> +		val = (header->status & OCC_STAT_MASTER) ? 1 : 0;
> +		break;
> +	case 1:
> +		val = (header->status & OCC_STAT_ACTIVE) ? 1 : 0;
> +		break;
> +	case 2:
> +		val = (header->ext_status & OCC_EXT_STAT_DVFS_OT) ? 1 : 0;
> +		break;
> +	case 3:
> +		val = (header->ext_status & OCC_EXT_STAT_DVFS_POWER) ? 1 : 0;
> +		break;
> +	case 4:
> +		val = (header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) ? 1 : 0;
> +		break;
> +	case 5:
> +		val = (header->ext_status & OCC_EXT_STAT_QUICK_DROP) ? 1 : 0;
> +		break;
> +	case 6:
> +		val = header->occ_state;
> +		break;
> +	case 7:
> +		if (header->status & OCC_STAT_MASTER)
> +			val = hweight8(header->occs_present);
> +		else
> +			val = 1;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
> +}
> +
> +static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_show_status, NULL, 0);
> +static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_show_status, NULL, 1);
> +static SENSOR_DEVICE_ATTR(occ_dvfs_ot, 0444, occ_show_status, NULL, 2);
> +static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_show_status, NULL, 3);
> +static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_show_status, NULL, 4);
> +static SENSOR_DEVICE_ATTR(occ_quick_drop, 0444, occ_show_status, NULL, 5);
> +static SENSOR_DEVICE_ATTR(occ_status, 0444, occ_show_status, NULL, 6);
> +static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_show_status, NULL, 7);
> +
> +static struct attribute *occ_attributes[] = {
> +	&sensor_dev_attr_occ_master.dev_attr.attr,
> +	&sensor_dev_attr_occ_active.dev_attr.attr,
> +	&sensor_dev_attr_occ_dvfs_ot.dev_attr.attr,
> +	&sensor_dev_attr_occ_dvfs_power.dev_attr.attr,
> +	&sensor_dev_attr_occ_mem_throttle.dev_attr.attr,
> +	&sensor_dev_attr_occ_quick_drop.dev_attr.attr,
> +	&sensor_dev_attr_occ_status.dev_attr.attr,
> +	&sensor_dev_attr_occs_present.dev_attr.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group occ_attr_group = {
> +	.attrs = occ_attributes,
> +};
> +
>  /* 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)
>  {
> @@ -1188,5 +1271,15 @@ int occ_setup(struct occ *occ, const char *name)
>  		return rc;
>  	}
>  
> +	rc = sysfs_create_group(&occ->bus_dev->kobj, &occ_attr_group);
> +	if (rc)
> +		dev_warn(occ->bus_dev, "failed to create status attrs: %d\n",
> +			 rc);
> +
>  	return 0;
>  }
> +
> +void occ_shutdown(struct occ *occ)
> +{
> +	sysfs_remove_group(&occ->bus_dev->kobj, &occ_attr_group);
> +}
> diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
> index 049c3b4..dc9e06d 100644
> --- a/drivers/hwmon/occ/common.h
> +++ b/drivers/hwmon/occ/common.h
> @@ -110,5 +110,6 @@ struct occ {
>  };
>  
>  int occ_setup(struct occ *occ, const char *name);
> +void occ_shutdown(struct occ *occ);
>  
>  #endif /* OCC_COMMON_H */
> diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c
> index 8032c0b..d719632 100644
> --- a/drivers/hwmon/occ/p8_i2c.c
> +++ b/drivers/hwmon/occ/p8_i2c.c
> @@ -230,6 +230,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" },
>  	{}
> @@ -243,6 +252,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);
> diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
> index be3a469..7dbe4d5 100644
> --- a/drivers/hwmon/occ/p9_sbe.c
> +++ b/drivers/hwmon/occ/p9_sbe.c
> @@ -135,6 +135,8 @@ static int p9_sbe_occ_remove(struct platform_device *pdev)
>  	p9_sbe_occ->sbe = NULL;
>  	p9_sbe_occ_close_client(p9_sbe_occ);
>  
> +	occ_shutdown(occ);
> +
>  	return 0;
>  }
>  
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2017-11-22 15:23 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-20 23:53 [PATCH v3 00/12] hwmon: Add On-Chip Controller hwmon driver Eddie James
2017-11-20 23:53 ` [PATCH v3 01/12] Documentation: hwmon: Add OCC documentation Eddie James
2017-11-20 23:53 ` [PATCH v3 02/12] Documentation: ABI: Add occ-hwmon driver sysfs documentation Eddie James
2017-11-22 15:15   ` [v3, " Guenter Roeck
2017-11-20 23:53 ` [PATCH v3 03/12] dt-bindings: i2c: Add P8 OCC hwmon device documentation Eddie James
2017-11-21 18:34   ` Rob Herring
2017-11-20 23:53 ` [PATCH v3 04/12] dt-bindings: fsi: Add P9 " Eddie James
2017-11-21 18:36   ` Rob Herring
2017-11-21 18:36     ` Rob Herring
2017-11-20 23:53 ` [PATCH v3 05/12] hwmon: Add On-Chip Controller (OCC) hwmon driver Eddie James
2017-11-20 23:53 ` [PATCH v3 06/12] hwmon (occ): Add command transport method for P8 and P9 Eddie James
2017-11-20 23:53   ` Eddie James
2017-11-20 23:53 ` [PATCH v3 07/12] hwmon (occ): Parse OCC poll response Eddie James
2017-11-20 23:53 ` [PATCH v3 08/12] hwmon (occ): Add sensor types and versions Eddie James
2017-11-20 23:53 ` [PATCH v3 09/12] hwmon (occ): Add sensor attributes and register hwmon device Eddie James
2017-11-22 15:22   ` [v3, " Guenter Roeck
2017-11-20 23:53 ` [PATCH v3 10/12] hwmon (occ): Add non-hwmon attributes Eddie James
2017-11-20 23:53   ` Eddie James
2017-11-22 15:23   ` [v3,10/12] " Guenter Roeck
2017-11-22 15:23     ` Guenter Roeck
2017-11-20 23:53 ` [PATCH v3 11/12] hwmon (occ): Add error handling Eddie James
2017-11-20 23:53 ` [PATCH v3 12/12] hwmon (occ): Add sysfs notification for errors and throttling Eddie James

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.