[RFC linux v4 3/6] hwmon: Add OCC driver basic sysfs attributes

eajames.ibm at gmail.com eajames.ibm at gmail.com
Fri Oct 14 08:42:09 AEDT 2016


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

Add name, update interval, and powercap sysfs files, as well as the method
to send commands to the OCC.

Signed-off-by: Edward A. James <eajames at us.ibm.com>
---
 drivers/hwmon/occ/power8_occ.c | 178 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 177 insertions(+), 1 deletion(-)

diff --git a/drivers/hwmon/occ/power8_occ.c b/drivers/hwmon/occ/power8_occ.c
index d32c48f..a42fb30 100644
--- a/drivers/hwmon/occ/power8_occ.c
+++ b/drivers/hwmon/occ/power8_occ.c
@@ -31,11 +31,184 @@
 
 #include "occ.h"
 
+/* Defined in POWER8 Processor Registers Specification */
+/* To generate attn to OCC */
+#define ATTN_DATA	0x0006B035
+/* For BMC to read/write SRAM */
+#define OCB_ADDRESS		0x0006B070
+#define OCB_DATA		0x0006B075
+#define OCB_STATUS_CONTROL_AND	0x0006B072
+#define OCB_STATUS_CONTROL_OR	0x0006B073
+/* See definition in:
+ * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf
+ */
+#define OCC_COMMAND_ADDR	0xFFFF6000
+#define OCC_RESPONSE_ADDR	0xFFFF7000
+
 struct power8_driver {
 	struct occ_driver *driver;
 	struct device *dev;
+	unsigned long update_interval;
+	u16 user_powercap;
 };
 
+static u8 occ_send_cmd(struct occ_driver *occ, u8 seq, u8 type,
+			    u16 length, u8 *data, u8 *resp)
+{
+	u32 cmd1, cmd2;
+	u16 checksum;
+	int i;
+
+	length = cpu_to_le16(length);
+	cmd1 = (seq << 24) | (type << 16) | length;
+	memcpy(&cmd2, data, length);
+	cmd2 <<= ((4 - length) * 8);
+
+	/* checksum: sum of every bytes of cmd1, cmd2 */
+	checksum = 0;
+	for (i = 0; i < 4; i++)
+		checksum += (cmd1 >> (i * 8)) & 0xFF;
+	for (i = 0; i < 4; i++)
+		checksum += (cmd2 >> (i * 8)) & 0xFF;
+	cmd2 |= checksum << ((2 - length) * 8);
+
+	/* Init OCB */
+	occ->bus_ops.putscom(occ->bus, OCB_STATUS_CONTROL_OR,  0x08000000,
+			     0x00000000);
+	occ->bus_ops.putscom(occ->bus, OCB_STATUS_CONTROL_AND, 0xFBFFFFFF,
+			     0xFFFFFFFF);
+
+	/* Send command */
+	occ->bus_ops.putscom(occ->bus, OCB_ADDRESS, OCC_COMMAND_ADDR,
+			     0x00000000);
+	occ->bus_ops.putscom(occ->bus, OCB_ADDRESS, OCC_COMMAND_ADDR,
+			     0x00000000);
+	occ->bus_ops.putscom(occ->bus, OCB_DATA, cmd1, cmd2);
+
+	/* Trigger attention */
+	occ->bus_ops.putscom(occ->bus, ATTN_DATA, 0x01010000, 0x00000000);
+
+	/* Get response data */
+	occ->bus_ops.putscom(occ->bus, OCB_ADDRESS, OCC_RESPONSE_ADDR,
+			     0x00000000);
+	occ->bus_ops.getscom(occ->bus, OCB_DATA, resp, 0);
+
+	/* return status */
+	return resp[2];
+}
+
+static ssize_t show_update_interval(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct power8_driver *driver = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n",
+			jiffies_to_msecs(driver->update_interval));
+}
+
+static ssize_t store_update_interval(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct power8_driver *driver = dev_get_drvdata(dev);
+	unsigned long val;
+	int rc;
+
+	rc = kstrtoul(buf, 10, &val);
+	if (rc)
+		return rc;
+
+	driver->update_interval = msecs_to_jiffies(val);
+
+	return count;
+}
+
+static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_update_interval,
+		   store_update_interval);
+
+static ssize_t show_name(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	return snprintf(buf, PAGE_SIZE - 1, "occ-power8-i2c\n");
+}
+
+static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
+
+static ssize_t show_user_powercap(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct power8_driver *driver = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", driver->user_powercap);
+}
+
+static ssize_t store_user_powercap(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct power8_driver *driver = dev_get_drvdata(dev);
+	u16 val;
+	u8 resp[8];
+	int rc;
+
+	rc = kstrtou16(buf, 10, &val);
+	if (rc)
+		return rc;
+
+	dev_dbg(dev, "set user powercap to: %u\n", val);
+	val = cpu_to_le16(val);
+	rc = occ_send_cmd(driver->driver, 0, 0x22, 2, (u8 *)&val, resp);
+	if (rc != 0) {
+		dev_err(dev,
+			"ERROR: Set User Powercap: wrong return status: %x\n",
+			rc);
+		if (rc == 0x13)
+			dev_info(dev,
+				 "ERROR: set invalid powercap value: %x\n",
+				 val);
+		return -EINVAL;
+	}
+
+	driver->user_powercap = val;
+	return count;
+}
+
+static DEVICE_ATTR(user_powercap, S_IWUSR | S_IRUGO, show_user_powercap,
+		   store_user_powercap);
+
+static void occ_remove_hwmon_attrs(struct power8_driver *driver)
+{
+	struct device *dev = driver->dev;
+
+	device_remove_file(dev, &dev_attr_update_interval);
+	device_remove_file(dev, &dev_attr_name);
+}
+
+static int occ_create_hwmon_attrs(struct power8_driver *driver)
+{
+	int rc;
+	struct device *dev = driver->dev;
+
+	rc = device_create_file(dev, &dev_attr_name);
+	if (rc)
+		goto error;
+
+	rc = device_create_file(dev, &dev_attr_update_interval);
+	if (rc)
+		goto error;
+
+	/* skip powercap for now; we need to determine if CAPS response id
+	 * is present first, which requires polling the OCC
+	 */
+
+	return 0;
+
+error:
+	dev_err(dev, "ERROR: cannot create hwmon attributes: %d\n", rc);
+	occ_remove_hwmon_attrs(driver);
+	return rc;
+}
+
 int occ_init(struct occ_driver *driver)
 {
 	struct device *dev = driver->hwmon;
@@ -50,9 +223,12 @@ int occ_init(struct occ_driver *driver)
 
 	dev_set_drvdata(dev, powr);
 
-	return 0;
+	return occ_create_hwmon_attrs(powr);
 }
 
 void occ_exit(struct occ_driver *driver)
 {
+	struct power8_driver *powr = dev_get_drvdata(driver->hwmon);
+
+	occ_remove_hwmon_attrs(powr);
 }
-- 
1.9.1



More information about the openbmc mailing list