[PATCH linux v5 5/5] drivers: OCC hwmon driver and sysfs
eajames.ibm at gmail.com
eajames.ibm at gmail.com
Tue Nov 8 10:16:22 AEDT 2016
From: "Edward A. James" <eajames at us.ibm.com>
Add functionality to poll OCC and make sensor data available over hwmon
sysfs.
Signed-off-by: Edward A. James <eajames at us.ibm.com>
---
drivers/hwmon/occ/occ.c | 769 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 766 insertions(+), 3 deletions(-)
diff --git a/drivers/hwmon/occ/occ.c b/drivers/hwmon/occ/occ.c
index b0443ea..cbec394 100644
--- a/drivers/hwmon/occ/occ.c
+++ b/drivers/hwmon/occ/occ.c
@@ -30,18 +30,768 @@
#include "occ.h"
+#define OCC_DATA_MAX 4096
+
+/* 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
+
+#define RESP_DATA_LENGTH 3
+#define RESP_HEADER_OFFSET 5
+#define SENSOR_STR_OFFSET 37
+#define SENSOR_BLOCK_NUM_OFFSET 43
+#define SENSOR_BLOCK_OFFSET 45
+
+#define MAX_SENSOR_ATTR_LEN 32
+
+struct sensor_data_block {
+ u8 sensor_type[4];
+ u8 reserved0;
+ u8 sensor_format;
+ u8 sensor_length;
+ u8 sensor_num;
+ void *sensors;
+};
+
+struct occ_poll_header {
+ u8 status;
+ u8 ext_status;
+ u8 occs_present;
+ u8 config;
+ u8 occ_state;
+ u8 mode;
+ u8 ips_status;
+ u8 error_log_id;
+ u32 error_log_addr_start;
+ u16 error_log_length;
+ u8 reserved2;
+ u8 reserved3;
+ u8 occ_code_level[16];
+ u8 sensor_eye_catcher[6];
+ u8 sensor_block_num;
+ u8 sensor_data_version;
+};
+
+struct occ_response {
+ u8 sequence_num;
+ u8 cmd_type;
+ u8 rtn_status;
+ u16 data_length;
+ struct occ_poll_header header;
+ struct sensor_data_block *blocks;
+ u16 chk_sum;
+ int sensor_block_id[MAX_OCC_SENSOR_TYPE];
+};
+
+struct sensor_attr_data {
+ enum sensor_type type;
+ u32 hwmon_index;
+ u32 attr_id;
+ char name[MAX_SENSOR_ATTR_LEN];
+ struct device_attribute dev_attr;
+};
+
+struct sensor_group {
+ char *name;
+ struct sensor_attr_data *sattr;
+ struct attribute_group group;
+};
+
struct occ_driver {
void *bus;
struct occ_bus_ops bus_ops;
struct occ_protocol_ops ops;
struct occ_protocol protocol;
struct device *dev;
+ unsigned long update_interval;
+ unsigned long last_updated;
+ u16 user_powercap;
+ struct mutex update_lock;
bool occ_online;
+ bool valid;
+ struct occ_response occ_response;
+ struct sensor_group sensor_groups[MAX_OCC_SENSOR_TYPE];
};
-void * occ_get_sensor(struct occ_driver *driver, int sensor_type)
+static void deinit_occ_resp_buf(struct occ_response *resp)
+{
+ int i;
+
+ if (!resp)
+ return;
+
+ if (!resp->blocks)
+ return;
+
+ for (i = 0; i < resp->header.sensor_block_num; ++i) {
+ kfree(resp->blocks[i].sensors);
+ }
+
+ kfree(resp->blocks);
+
+ memset(resp, 0, sizeof(struct occ_response));
+
+ for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+ resp->sensor_block_id[i] = -1;
+}
+
+static void *occ_get_sensor_by_type(struct occ_response *resp,
+ enum sensor_type t)
+{
+ if (!resp->blocks)
+ return NULL;
+
+ if (resp->sensor_block_id[t] == -1)
+ return NULL;
+
+ return resp->blocks[resp->sensor_block_id[t]].sensors;
+}
+
+static int occ_renew_sensor(struct occ_driver *driver, u8 sensor_length,
+ u8 sensor_num, enum sensor_type t, int block)
+{
+ void *sensor;
+ int rc;
+ struct occ_response *resp = &driver->occ_response;
+
+ sensor = occ_get_sensor_by_type(resp, t);
+
+ /* empty sensor block, release older sensor data */
+ if (sensor_num == 0 || sensor_length == 0) {
+ kfree(sensor);
+ return -1;
+ }
+
+ if (!sensor || sensor_num !=
+ resp->blocks[resp->sensor_block_id[t]].sensor_num) {
+ kfree(sensor);
+ resp->blocks[block].sensors =
+ driver->ops.alloc_sensor(t, sensor_num);
+ if (!resp->blocks[block].sensors) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ deinit_occ_resp_buf(resp);
+ return rc;
+}
+
+static int parse_occ_response(struct occ_driver *driver, u8 *data,
+ struct occ_response *resp)
+{
+ int b;
+ int s;
+ int rc;
+ int dnum = SENSOR_BLOCK_OFFSET;
+ u8 sensor_block_num;
+ u8 sensor_type[4];
+ u8 sensor_format;
+ u8 sensor_length;
+ u8 sensor_num;
+ struct device *dev = driver->dev;
+ void (*parse_sensor)(u8 *data, void *sensor, int sensor_type, int off,
+ int snum) = driver->ops.parse_sensor;
+
+ /* check if the data is valid */
+ if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) {
+ dev_dbg(dev, "ERROR: no SENSOR String in response\n");
+ rc = -1;
+ goto err;
+ }
+
+ sensor_block_num = data[SENSOR_BLOCK_NUM_OFFSET];
+ if (sensor_block_num == 0) {
+ dev_dbg(dev, "ERROR: SENSOR block num is 0\n");
+ rc = -1;
+ goto err;
+ }
+
+ /* if sensor block has changed, re-malloc */
+ if (sensor_block_num != resp->header.sensor_block_num) {
+ deinit_occ_resp_buf(resp);
+ resp->blocks = kcalloc(sensor_block_num,
+ sizeof(struct sensor_data_block),
+ GFP_KERNEL);
+ if (!resp->blocks)
+ return -ENOMEM;
+ }
+
+ memcpy(&resp->header, &data[RESP_HEADER_OFFSET],
+ sizeof(struct occ_poll_header));
+ resp->header.error_log_addr_start =
+ be32_to_cpu(resp->header.error_log_addr_start);
+ resp->header.error_log_length =
+ be16_to_cpu(resp->header.error_log_length);
+
+ dev_dbg(dev, "Reading %d sensor blocks\n",
+ resp->header.sensor_block_num);
+ for (b = 0; b < sensor_block_num; b++) {
+ /* 8-byte sensor block head */
+ strncpy(sensor_type, &data[dnum], 4);
+ sensor_format = data[dnum + 5];
+ sensor_length = data[dnum + 6];
+ sensor_num = data[dnum + 7];
+ dnum = dnum + 8;
+
+ dev_dbg(dev, "sensor block[%d]: type: %s, sensor_num: %d\n", b,
+ sensor_type, sensor_num);
+
+ if (strncmp(sensor_type, "FREQ", 4) == 0) {
+ rc = occ_renew_sensor(driver, sensor_length, sensor_num,
+ FREQ, b);
+ if (rc)
+ continue;
+
+ resp->sensor_block_id[FREQ] = b;
+ for (s = 0; s < sensor_num; s++) {
+ parse_sensor(data, resp->blocks[b].sensors,
+ FREQ, dnum, s);
+ dnum = dnum + sensor_length;
+ }
+ } else if (strncmp(sensor_type, "TEMP", 4) == 0) {
+ rc = occ_renew_sensor(driver, sensor_length,
+ sensor_num, TEMP, b);
+ if (rc)
+ continue;
+
+ resp->sensor_block_id[TEMP] = b;
+ for (s = 0; s < sensor_num; s++) {
+ parse_sensor(data, resp->blocks[b].sensors,
+ TEMP, dnum, s);
+ dnum = dnum + sensor_length;
+ }
+ } else if (strncmp(sensor_type, "POWR", 4) == 0) {
+ rc = occ_renew_sensor(driver, sensor_length,
+ sensor_num, POWER, b);
+ if (rc)
+ continue;
+
+ resp->sensor_block_id[POWER] = b;
+ for (s = 0; s < sensor_num; s++) {
+ parse_sensor(data, resp->blocks[b].sensors,
+ POWER, dnum, s);
+ dnum = dnum + sensor_length;
+ }
+ } else if (strncmp(sensor_type, "CAPS", 4) == 0) {
+ rc = occ_renew_sensor(driver, sensor_length,
+ sensor_num, CAPS, b);
+ if (rc)
+ continue;
+
+ resp->sensor_block_id[CAPS] = b;
+ for (s = 0; s < sensor_num; s++) {
+ parse_sensor(data, resp->blocks[b].sensors,
+ CAPS, dnum, s);
+ dnum = dnum + sensor_length;
+ }
+
+ } else {
+ dev_dbg(dev, "ERROR: sensor type %s not supported\n",
+ resp->blocks[b].sensor_type);
+ rc = -1;
+ goto err;
+ }
+
+ strncpy(resp->blocks[b].sensor_type, sensor_type, 4);
+ resp->blocks[b].sensor_format = sensor_format;
+ resp->blocks[b].sensor_length = sensor_length;
+ resp->blocks[b].sensor_num = sensor_num;
+ }
+
+ return 0;
+err:
+ deinit_occ_resp_buf(resp);
+ return rc;
+}
+
+static u8 occ_send_cmd(struct occ_driver *driver, 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 */
+ driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_OR, 0x08000000,
+ 0x00000000);
+ driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_AND,
+ 0xFBFFFFFF, 0xFFFFFFFF);
+
+ /* Send command */
+ driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+ driver->protocol.command_addr, 0x00000000);
+ driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+ driver->protocol.command_addr, 0x00000000);
+ driver->bus_ops.putscom(driver->bus, OCB_DATA, cmd1, cmd2);
+
+ /* Trigger attention */
+ driver->bus_ops.putscom(driver->bus, ATTN_DATA, 0x01010000,
+ 0x00000000);
+
+ /* Get response data */
+ driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+ driver->protocol.response_addr, 0x00000000);
+ driver->bus_ops.getscom(driver->bus, OCB_DATA, resp, 0);
+
+ /* return status */
+ return resp[2];
+}
+
+static inline u16 get_occdata_length(u8 *data)
+{
+ return be16_to_cpup((const __be16 *)&data[RESP_DATA_LENGTH]);
+}
+
+static int occ_get_all(struct occ_driver *driver)
+{
+ int i = 0, rc;
+ u8 *occ_data;
+ u16 num_bytes;
+ u8 poll_cmd_data = 0x10;
+ struct device *dev = driver->dev;
+ struct occ_response *resp = &driver->occ_response;
+
+ occ_data = devm_kzalloc(dev, OCC_DATA_MAX, GFP_KERNEL);
+ if (!occ_data)
+ return -ENOMEM;
+
+ rc = occ_send_cmd(driver, 0, 0, 1, &poll_cmd_data, occ_data);
+ if (rc) {
+ dev_err(dev, "ERROR: OCC Poll: 0x%x\n", rc);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ num_bytes = get_occdata_length(occ_data);
+ dev_dbg(dev, "OCC data length: %d\n", num_bytes);
+
+ if (num_bytes > OCC_DATA_MAX) {
+ dev_dbg(dev, "ERROR: OCC data length must be < 4KB\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (num_bytes <= 0) {
+ dev_dbg(dev, "ERROR: OCC data length is zero\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ /* read remaining data */
+ for (i = 8; i < num_bytes + 8; i += 8)
+ driver->bus_ops.getscom(driver->bus, OCB_DATA, occ_data, i);
+
+ rc = parse_occ_response(driver, occ_data, resp);
+
+out:
+ devm_kfree(dev, occ_data);
+ return rc;
+}
+
+static int occ_update_device(struct occ_driver *driver)
+{
+ int rc = 0;
+
+ mutex_lock(&driver->update_lock);
+
+ if (time_after(jiffies, driver->last_updated + driver->update_interval)
+ || !driver->valid) {
+ driver->valid = 1;
+
+ rc = occ_get_all(driver);
+ if (rc)
+ driver->valid = 0;
+
+ driver->last_updated = jiffies;
+ }
+
+ mutex_unlock(&driver->update_lock);
+
+ return rc;
+}
+
+void *occ_get_sensor(struct occ_driver *driver, int sensor_type)
+{
+ int rc;
+
+ rc = occ_update_device(driver);
+ if (rc != 0) {
+ dev_dbg(driver->dev, "ERROR: cannot get occ sensor data: %d\n",
+ rc);
+ return NULL;
+ }
+
+ return occ_get_sensor_by_type(&driver->occ_response, sensor_type);
+}
+
+static ssize_t show_input(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int val;
+ struct sensor_attr_data *sdata = container_of(attr,
+ struct sensor_attr_data,
+ dev_attr);
+ struct occ_driver *driver = dev_get_drvdata(dev);
+
+ val = driver->ops.get_sensor_value(driver, sdata->type,
+ sdata->hwmon_index - 1);
+ if (sdata->type == TEMP)
+ val *= 1000; /* in millidegree Celsius */
+
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int val;
+ struct sensor_attr_data *sdata = container_of(attr,
+ struct sensor_attr_data,
+ dev_attr);
+ struct occ_driver *driver = dev_get_drvdata(dev);
+
+ val = driver->ops.get_sensor_id(driver, sdata->type,
+ sdata->hwmon_index - 1);
+
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_caps(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int val;
+ struct caps_sensor *sensor;
+ struct sensor_attr_data *sdata = container_of(attr,
+ struct sensor_attr_data,
+ dev_attr);
+ struct occ_driver *driver = dev_get_drvdata(dev);
+
+ sensor = occ_get_sensor(driver, CAPS);
+ if (!sensor) {
+ val = -1;
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+ }
+
+ val = driver->ops.get_caps_value(sensor, sdata->hwmon_index -1,
+ sdata->attr_id);
+
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_update_interval(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct occ_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 occ_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 NULL;
+ return snprintf(buf, PAGE_SIZE - 1, "occ\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 occ_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 occ_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, 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 deinit_sensor_groups(struct device *dev,
+ struct sensor_group *sensor_groups)
+{
+ int cnt;
+
+ for (cnt = 0; cnt < MAX_OCC_SENSOR_TYPE; cnt++) {
+ if (sensor_groups[cnt].group.attrs)
+ devm_kfree(dev, sensor_groups[cnt].group.attrs);
+ if (sensor_groups[cnt].sattr)
+ devm_kfree(dev, sensor_groups[cnt].sattr);
+ sensor_groups[cnt].group.attrs = NULL;
+ sensor_groups[cnt].sattr = NULL;
+ }
+}
+
+static void sensor_attr_init(struct sensor_attr_data *sdata,
+ char *sensor_group_name,
+ char *attr_name,
+ ssize_t (*show)(struct device *dev,
+ struct device_attribute *attr,
+ char *buf))
+{
+ sysfs_attr_init(&sdata->dev_attr.attr);
+
+ snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
+ sensor_group_name, sdata->hwmon_index, attr_name);
+ sdata->dev_attr.attr.name = sdata->name;
+ sdata->dev_attr.attr.mode = S_IRUGO;
+ sdata->dev_attr.show = show;
+}
+
+static int create_sensor_group(struct occ_driver *driver,
+ enum sensor_type type, int sensor_num)
+{
+ struct device *dev = driver->dev;
+ struct sensor_group *sensor_groups = driver->sensor_groups;
+ struct sensor_attr_data *sdata;
+ int rc, cnt;
+
+ /* each sensor has 'label' and 'input' attributes */
+ sensor_groups[type].group.attrs =
+ devm_kzalloc(dev, sizeof(struct attribute *) *
+ sensor_num * 2 + 1, GFP_KERNEL);
+ if (!sensor_groups[type].group.attrs) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ sensor_groups[type].sattr =
+ devm_kzalloc(dev, sizeof(struct sensor_attr_data) *
+ sensor_num * 2, GFP_KERNEL);
+ if (!sensor_groups[type].sattr) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ for (cnt = 0; cnt < sensor_num; cnt++) {
+ sdata = &sensor_groups[type].sattr[cnt];
+ /* hwmon attributes index starts from 1 */
+ sdata->hwmon_index = cnt + 1;
+ sdata->type = type;
+ sensor_attr_init(sdata, sensor_groups[type].name, "input",
+ show_input);
+ sensor_groups[type].group.attrs[cnt] = &sdata->dev_attr.attr;
+
+ sdata = &sensor_groups[type].sattr[cnt + sensor_num];
+ sdata->hwmon_index = cnt + 1;
+ sdata->type = type;
+ sensor_attr_init(sdata, sensor_groups[type].name, "label",
+ show_label);
+ sensor_groups[type].group.attrs[cnt + sensor_num] =
+ &sdata->dev_attr.attr;
+ }
+
+ rc = sysfs_create_group(&dev->kobj, &sensor_groups[type].group);
+ if (rc)
+ goto err;
+
+ return 0;
+err:
+ deinit_sensor_groups(dev, sensor_groups);
+ return rc;
+}
+
+static void caps_sensor_attr_init(struct sensor_attr_data *sdata,
+ char *attr_name, uint32_t hwmon_index,
+ uint32_t attr_id)
+{
+ sdata->type = CAPS;
+ sdata->hwmon_index = hwmon_index;
+ sdata->attr_id = attr_id;
+
+ snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
+ "caps", sdata->hwmon_index, attr_name);
+
+ sysfs_attr_init(&sdata->dev_attr.attr);
+ sdata->dev_attr.attr.name = sdata->name;
+ sdata->dev_attr.attr.mode = S_IRUGO;
+ sdata->dev_attr.show = show_caps;
+}
+
+static int create_caps_sensor_group(struct occ_driver *driver,
+ int sensor_num)
+{
+ struct device *dev = driver->dev;
+ struct sensor_group *sensor_groups = driver->sensor_groups;
+ int field_num = driver->protocol.num_caps_fields;
+ struct sensor_attr_data *sdata;
+ int i, j, rc;
+
+ sensor_groups[CAPS].group.attrs =
+ devm_kzalloc(dev, sizeof(struct attribute *) * sensor_num *
+ field_num + 1, GFP_KERNEL);
+ if (!sensor_groups[CAPS].group.attrs) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ sensor_groups[CAPS].sattr =
+ devm_kzalloc(dev, sizeof(struct sensor_attr_data) *
+ sensor_num * field_num, GFP_KERNEL);
+ if (!sensor_groups[CAPS].sattr) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ for (j = 0; j < sensor_num; ++j) {
+ for (i = 0; i < field_num; ++i) {
+ sdata = &sensor_groups[CAPS].sattr[j * field_num + i];
+ caps_sensor_attr_init(sdata,
+ driver->protocol.caps_names[i],
+ j + 1, i);
+ sensor_groups[CAPS].group.attrs[j * field_num + i] =
+ &sdata->dev_attr.attr;
+ }
+ }
+
+ rc = sysfs_create_group(&dev->kobj, &sensor_groups[CAPS].group);
+ if (rc)
+ goto err;
+
+ return rc;
+err:
+ deinit_sensor_groups(dev, sensor_groups);
+ return rc;
+}
+
+static void occ_remove_hwmon_attrs(struct occ_driver *driver)
+{
+ struct device *dev = driver->dev;
+
+ device_remove_file(dev, &dev_attr_user_powercap);
+ device_remove_file(dev, &dev_attr_update_interval);
+ device_remove_file(dev, &dev_attr_name);
+}
+
+static int occ_create_hwmon_attrs(struct occ_driver *driver)
+{
+ int i, rc, id, sensor_num;
+ struct device *dev = driver->dev;
+ struct sensor_group *sensor_groups = driver->sensor_groups;
+ struct occ_response *resp = &driver->occ_response;
+
+ for (i = 0; i < MAX_OCC_SENSOR_TYPE; ++i)
+ resp->sensor_block_id[i] = -1;
+
+ /* read sensor data from occ. */
+ rc = occ_update_device(driver);
+ if (rc != 0) {
+ dev_err(dev, "ERROR: cannot get occ sensor data: %d\n", rc);
+ return rc;
+ }
+ if (!resp->blocks)
+ return -1;
+
+ 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;
+
+ if (resp->sensor_block_id[CAPS] >= 0) {
+ /* user powercap: only for master OCC */
+ rc = device_create_file(dev, &dev_attr_user_powercap);
+ if (rc)
+ goto error;
+ }
+
+ sensor_groups[FREQ].name = "freq";
+ sensor_groups[TEMP].name = "temp";
+ sensor_groups[POWER].name = "power";
+ sensor_groups[CAPS].name = "caps";
+
+ for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++) {
+ id = resp->sensor_block_id[i];
+ if (id < 0)
+ continue;
+
+ sensor_num = resp->blocks[id].sensor_num;
+ if (i == CAPS)
+ rc = create_caps_sensor_group(driver, sensor_num);
+ else
+ rc = create_sensor_group(driver, i, sensor_num);
+ if (rc)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ dev_err(dev, "ERROR: cannot create hwmon attributes: %d\n", rc);
+ occ_remove_hwmon_attrs(driver);
+ return rc;
}
static ssize_t show_occ_online(struct device *dev,
@@ -73,10 +823,18 @@ static ssize_t store_occ_online(struct device *dev,
return PTR_ERR(driver->dev);
dev_set_drvdata(driver->dev, driver);
+
+ rc = occ_create_hwmon_attrs(driver);
+ if (rc) {
+ hwmon_device_unregister(driver->dev);
+ driver->dev = NULL;
+ return rc;
+ }
} else if (val == 0) {
if (!driver->occ_online)
return count;
+ occ_remove_hwmon_attrs(driver);
hwmon_device_unregister(driver->dev);
driver->dev = NULL;
} else
@@ -105,6 +863,9 @@ int occ_start(struct device *dev, void *bus, struct occ_bus_ops bus_ops,
driver->ops = ops;
driver->protocol = protocol;
+ driver->update_interval = HZ;
+ mutex_init(&driver->update_lock);
+
return device_create_file(dev, &dev_attr_online);
}
@@ -115,8 +876,10 @@ int occ_stop(struct device *dev)
device_remove_file(dev, &dev_attr_online);
if (driver) {
- if (driver->dev)
+ if (driver->dev) {
+ occ_remove_hwmon_attrs(driver);
hwmon_device_unregister(driver->dev);
+ }
devm_kfree(dev, driver);
}
--
1.9.1
More information about the openbmc
mailing list