[PATCH linux dev-4.10 2/3] drivers: hwmon: OCC: Add common functionality and SBE access
Eddie James
eajames at linux.vnet.ibm.com
Sat May 27 03:46:32 AEST 2017
From: "Edward A. James" <eajames at us.ibm.com>
Add functions common to all POWER processors and bus transfer methods.
Add the main function to actually fetch the OCC poll response via SBE.
Signed-off-by: Edward A. James <eajames at us.ibm.com>
---
drivers/hwmon/occ/common.c | 97 +++++++++++++++++++++++++++++++++++++++++++++-
drivers/hwmon/occ/common.h | 10 +++++
drivers/hwmon/occ/p9_sbe.c | 82 +++++++++++++++++++++++++++++++++++++++
3 files changed, 188 insertions(+), 1 deletion(-)
diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index a2fb57b..1f17def 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -9,7 +9,102 @@
#include "common.h"
+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;
+ }
+
+ 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 {
+ dev_warn(occ->bus_dev, "sensor not supported %.4s\n",
+ block->header.eye_catcher);
+ continue;
+ }
+
+ sensor->num_sensors = block->header.num_sensors;
+ sensor->data = &block->data;
+ }
+}
+
int occ_poll(struct occ *occ)
{
- return 0;
+ u16 checksum = occ->poll_cmd_data + 1;
+ u8 cmd[8];
+
+ cmd[0] = 0;
+ cmd[1] = 0;
+ cmd[2] = 0;
+ cmd[3] = 1;
+ cmd[4] = occ->poll_cmd_data;
+ cmd[5] = checksum >> 8;
+ cmd[6] = checksum & 0xFF;
+ cmd[7] = 0;
+
+ return occ->send_cmd(occ, cmd);
+}
+
+int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
+{
+ int rc;
+ u8 cmd[8];
+ u16 checksum = 0x24;
+
+ user_power_cap = cpu_to_be16(user_power_cap);
+
+ cmd[0] = 0;
+ cmd[1] = 0x22;
+ cmd[2] = 0;
+ cmd[3] = 2;
+
+ memcpy(&cmd[4], &user_power_cap, 2);
+
+ checksum += cmd[4] + cmd[5];
+ cmd[6] = checksum >> 8;
+ cmd[7] = checksum & 0xFF;
+
+ mutex_lock(&occ->lock);
+ rc = occ->send_cmd(occ, cmd);
+ mutex_unlock(&occ->lock);
+
+ return rc;
+}
+
+int occ_update_response(struct occ *occ)
+{
+ int rc = 0;
+
+ mutex_lock(&occ->lock);
+
+ if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) {
+ rc = occ_poll(occ);
+ occ->last_update = jiffies;
+ }
+
+ mutex_unlock(&occ->lock);
+ return rc;
}
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index dcb1218..8d07878 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -13,6 +13,7 @@
#include <linux/hwmon-sysfs.h>
#include <linux/sysfs.h>
+#define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000)
#define OCC_RESP_DATA_BYTES 4089
struct occ_response {
@@ -75,10 +76,19 @@ struct occ_sensors {
struct occ {
struct device *bus_dev;
+ unsigned long last_update;
+ struct mutex lock;
+
struct occ_response resp;
struct occ_sensors sensors;
+
+ u8 poll_cmd_data;
+ int (*send_cmd)(struct occ *occ, u8 *cmd);
};
+void occ_parse_poll_response(struct occ *occ);
int occ_poll(struct occ *occ);
+int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap);
+int occ_update_response(struct occ *occ);
#endif /* __OCC_COMMON_H__ */
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index 83eaec3..889594c 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -16,6 +16,18 @@
#include <linux/sched.h>
#include <linux/workqueue.h>
+#define OCC_TIMEOUT_MS 5000
+#define OCC_CMD_IN_PRG_MS 100
+
+#define RESP_RETURN_CMD_IN_PRG 0xFF
+#define RESP_RETURN_SUCCESS 0
+#define RESP_RETURN_CMD_INVAL 0x11
+#define RESP_RETURN_CMD_LEN 0x12
+#define RESP_RETURN_DATA_INVAL 0x13
+#define RESP_RETURN_CHKSUM 0x14
+#define RESP_RETURN_OCC_ERR 0x15
+#define RESP_RETURN_STATE 0x16
+
struct p9_sbe_occ {
struct occ occ;
struct device *sbe;
@@ -23,6 +35,72 @@ struct p9_sbe_occ {
#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)
+{
+ int rc;
+ unsigned long start;
+ struct occ_client *client;
+ struct occ_response *resp = &occ->resp;
+ struct p9_sbe_occ *p9_sbe_occ = to_p9_sbe_occ(occ);
+
+ start = jiffies;
+
+retry:
+ client = occ_drv_open(p9_sbe_occ->sbe, 0);
+ if (!client)
+ return -ENODEV;
+
+ rc = occ_drv_write(client, (const char *)&cmd[1], 7);
+ if (rc < 0)
+ goto err;
+
+ rc = occ_drv_read(client, (char *)resp, sizeof(*resp));
+ if (rc < 0)
+ goto err;
+
+ occ_drv_release(client);
+
+ switch (resp->return_status) {
+ case RESP_RETURN_CMD_IN_PRG:
+ if (time_after(jiffies, start + msecs_to_jiffies(OCC_TIMEOUT_MS)))
+ rc = -EALREADY;
+ else {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(OCC_CMD_IN_PRG_MS));
+
+ goto retry;
+ }
+ break;
+ case RESP_RETURN_SUCCESS:
+ rc = 0;
+ break;
+ case RESP_RETURN_CMD_INVAL:
+ case RESP_RETURN_CMD_LEN:
+ case RESP_RETURN_DATA_INVAL:
+ case RESP_RETURN_CHKSUM:
+ rc = -EINVAL;
+ break;
+ case RESP_RETURN_OCC_ERR:
+ rc = -EREMOTE;
+ break;
+ default:
+ rc = -EFAULT;
+ }
+
+ if (rc < 0) {
+ dev_warn(occ->bus_dev, "occ bad response:%d\n",
+ resp->return_status);
+ return rc;
+ }
+
+ return 0;
+
+err:
+ occ_drv_release(client);
+ dev_err(occ->bus_dev, "occ bus op failed rc:%d\n", rc);
+ return rc;
+}
+
static int p9_sbe_occ_probe(struct platform_device *pdev)
{
struct occ *occ;
@@ -36,6 +114,10 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
occ = &p9_sbe_occ->occ;
occ->bus_dev = &pdev->dev;
+ occ->poll_cmd_data = 0x20;
+ occ->send_cmd = p9_sbe_occ_send_cmd;
+ mutex_init(&occ->lock);
+ platform_set_drvdata(pdev, p9_sbe_occ);
return 0;
}
--
1.8.3.1
More information about the openbmc
mailing list