+ block->sensors = NULL;
+ return -ENODATA;
+ }
+
+ /* different num sensors or length, re-alloc */
+ if (num_sensors != block->header.num_sensors ||
+ sensor_length != block->header.sensor_length)
+ devm_kfree(driver->dev, block->sensors);
+ else
+ return 0;
+ }
+
+ /* each sensor type is a different size */
+ block->sensors = driver->ops.alloc_sensor(t, num_sensors);
+ if (!block->sensors)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int parse_occ_response(struct occ *driver, u16 num_bytes)
+{
+ int b;
+ int s;
+ int rc;
+ unsigned int offset = SENSOR_BLOCK_OFFSET;
+ int sensor_type;
+ u8 num_sensor_blocks;
+ struct sensor_data_block_header *block;
+ struct device *dev = driver->dev;
+ u8 *data = driver->raw_data;
+ struct occ_response *resp = &driver->response;
+
+ /* check if the data is valid */
+ if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) {
+ dev_err(dev, "no SENSOR string in response\n");
+ return -ENODATA;
+ }
+
+ num_sensor_blocks = data[NUM_SENSOR_BLOCKS_OFFSET];
+ if (num_sensor_blocks == 0) {
+ dev_warn(dev, "no sensor blocks available\n");
+ return -ENODATA;
+ }
+
+ memcpy(&resp->header, &data[RESP_HEADER_OFFSET],
+ sizeof(struct occ_poll_header));
+
+ /* data length starts at actual data */
+ num_bytes += RESP_HEADER_OFFSET;
+
+ /* translate fields > 1 byte */
+ 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);
+
+ for (b = 0; b < num_sensor_blocks && b < MAX_OCC_SENSOR_TYPE; b++) {
+ if (offset + sizeof(struct sensor_data_block_header) >
+ num_bytes) {
+ dev_warn(dev, "exceeded data length\n");
+ return 0;
+ }
+
+ block = (struct sensor_data_block_header *)&data[offset];
+ offset += sizeof(struct sensor_data_block_header);
+
+ if (strncmp(block->sensor_type, "FREQ", 4) == 0)
+ sensor_type = FREQ;
+ else if (strncmp(block->sensor_type, "TEMP", 4) == 0)
+ sensor_type = TEMP;
+ else if (strncmp(block->sensor_type, "POWR", 4) == 0)
+ sensor_type = POWER;
+ else if (strncmp(block->sensor_type, "CAPS", 4) == 0)
+ sensor_type = CAPS;
+ else {
+ dev_warn(dev, "sensor type not supported %.4s\n",
+ block->sensor_type);
+ continue;
+ }
+
+ rc = occ_check_sensor(driver, block->sensor_length,
+ block->num_sensors, sensor_type, b);
+ if (rc == -ENOMEM)
+ return rc;
+ else if (rc)
+ continue;
+
+ resp->data.sensor_block_id[sensor_type] = b;
+ /* copy block data over to response pointer */
+ resp->data.blocks[b].header = *block;
+ for (s = 0; s < block->num_sensors; s++) {
+ if (offset + block->sensor_length > num_bytes) {
+ dev_warn(dev, "exceeded data length\n");
+ return 0;
+ }
+
+ driver->ops.parse_sensor(data,
+ resp->data.blocks[b].sensors,
+ sensor_type, offset, s);
+ offset += block->sensor_length;
+ }
+ }
+
+ return 0;
+}
+
+static u8 occ_send_cmd(struct occ *driver, u8 seq, u8 type, u16
length,
+ const u8 *data, u8 *resp)
+{
+ u32 cmd1, cmd2 = 0;
+ u16 checksum = 0;
+ bool retry = false;
+ int i, rc, tries = 0;
+
+ cmd1 = (seq << 24) | (type << 16) | length;
+ memcpy(&cmd2, data, length);
+ cmd2 <<= ((4 - length) * 8);
+
+ /* checksum: sum of every bytes of cmd1, cmd2 */
+ for (i = 0; i < 4; i++) {
+ checksum += (cmd1 >> (i * 8)) & 0xFF;
+ checksum += (cmd2 >> (i * 8)) & 0xFF;
+ }
+
+ cmd2 |= checksum << ((2 - length) * 8);
+
+ /* Init OCB */
+ rc = driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_OR,
+ OCB_OR_INIT0, OCB_OR_INIT1);
+ if (rc)
+ goto err;
+
+ rc = driver->bus_ops.putscom(driver->bus, OCB_STATUS_CONTROL_AND,
+ OCB_AND_INIT0, OCB_AND_INIT1);
+ if (rc)
+ goto err;
+
+ /* Send command, 2nd half of the 64-bit addr is unused (write 0) */
+ rc = driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+ driver->config.command_addr, 0);
+ if (rc)
+ goto err;
+
+ rc = driver->bus_ops.putscom(driver->bus, OCB_DATA, cmd1, cmd2);
+ if (rc)
+ goto err;
+
+ /* Trigger attention */
+ rc = driver->bus_ops.putscom(driver->bus, ATTN_DATA, ATTN0, ATTN1);
+ if (rc)
+ goto err;
+
+ /* Get response data */
+ rc = driver->bus_ops.putscom(driver->bus, OCB_ADDRESS,
+ driver->config.response_addr, 0);
+ if (rc)
+ goto err;
+
+ do {
+ if (retry) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(CMD_IN_PRG_INT_MS));
+ }
+
+ rc = driver->bus_ops.getscom(driver->bus, OCB_DATA,
+ (u64 *)resp);
+ if (rc)
+ goto err;
+
+ /* retry if we get "command in progress" return status */
+ retry = resp[RESP_RETURN_STATUS] == RESP_RETURN_CMD_IN_PRG &&
+ tries++ < CMD_IN_PRG_RETRIES;
+ } while (retry);
+
+ /* check the occ response */
+ switch (resp[RESP_RETURN_STATUS]) {
+ case RESP_RETURN_CMD_IN_PRG:
+ rc = -EALREADY;
+ 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(driver->dev, "occ bad response:%d\n",
+ resp[RESP_RETURN_STATUS]);
+
+ return rc;
+
+err:
+ dev_err(driver->dev, "scom op failed rc:%d\n", rc);
+ return rc;
+}
+
+static int occ_get_all(struct occ *driver)
+{
+ int i = 0, rc;
+ u8 *occ_data = driver->raw_data;
+ u16 num_bytes;
+ const u8 poll_cmd_data = OCC_POLL_STAT_SENSOR;
+ struct device *dev = driver->dev;
+
+ memset(occ_data, 0, OCC_DATA_MAX);
+
+ rc = occ_send_cmd(driver, 0, OCC_POLL, 1, &poll_cmd_data, occ_data);
+ if (rc)
+ return rc;
+
+ num_bytes = get_unaligned((u16 *)&occ_data[RESP_DATA_LENGTH]);
+ num_bytes = be16_to_cpu(num_bytes);
+
+ if (num_bytes > OCC_DATA_MAX || num_bytes < OCC_DATA_MIN) {
+ dev_err(dev, "bad OCC data length:%d\n", num_bytes);
+ return -EINVAL;
+ }
+
+ /* read remaining data, 8 byte scoms at a time */
+ for (i = 8; i < num_bytes + 8; i += 8) {
+ rc = driver->bus_ops.getscom(driver->bus, OCB_DATA,
+ (u64 *)&occ_data[i]);
+ if (rc) {
+ dev_err(dev, "getscom op failed rc:%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* don't need more sanity checks; buffer is alloc'd for max response
+ * size so we just check for valid data in parse_occ_response
+ */
+ rc = parse_occ_response(driver, num_bytes);
+
+ return rc;
+}
+
+int occ_update_device(struct occ *driver)
+{
+ int rc = 0;
+
+ mutex_lock(&driver->update_lock);
+
+ if (time_after(jiffies, driver->last_updated + HZ) ||
!driver->valid) {
+ driver->valid = true;
+
+ rc = occ_get_all(driver);
+ if (rc)
+ driver->valid = false;
+
+ driver->last_updated = jiffies;
+ }
+
+ mutex_unlock(&driver->update_lock);
+
+ return rc;
+}
+EXPORT_SYMBOL(occ_update_device);
+
+void *occ_get_sensor(struct occ *driver, int sensor_type)
+{
+ int rc;
+ int type_id;
+
+ /* occ_update_device locks the update lock */
+ rc = occ_update_device(driver);
+ if (rc)
+ return ERR_PTR(rc);
+
+ type_id = driver->response.data.sensor_block_id[sensor_type];
+ if (type_id == -1)
+ return ERR_PTR(-ENODATA);
+
+ return driver->response.data.blocks[type_id].sensors;
+}
+EXPORT_SYMBOL(occ_get_sensor);
+
+int occ_get_sensor_field(struct occ *occ, int sensor_type, int
sensor_num,
+ u32 hwmon, long *val)
+{
+ return occ->ops.get_sensor(occ, sensor_type, sensor_num, hwmon,
val);
+}
+EXPORT_SYMBOL(occ_get_sensor_field);
+
+void occ_get_response_blocks(struct occ *occ, struct occ_blocks
**blocks)
+{
+ *blocks = &occ->response.data;
+}
+EXPORT_SYMBOL(occ_get_response_blocks);
+
+int occ_set_user_powercap(struct occ *occ, u16 cap)
+{
+ u8 resp[8];
+
+ cap = cpu_to_be16(cap);
+
+ return occ_send_cmd(occ, 0, OCC_SET_USER_POWR_CAP, 2, (const u8
*)&cap,
+ resp);
+}
+EXPORT_SYMBOL(occ_set_user_powercap);
+
+struct occ *occ_start(struct device *dev, void *bus,
+ struct occ_bus_ops *bus_ops, const struct occ_ops *ops,
+ const struct occ_config *config)
+{
+ struct occ *driver = devm_kzalloc(dev, sizeof(struct occ),
GFP_KERNEL);
+
+ if (!driver)
+ return ERR_PTR(-ENOMEM);
+
+ driver->dev = dev;
+ driver->bus = bus;
+ driver->bus_ops = *bus_ops;
+ driver->ops = *ops;
+ driver->config = *config;
+ driver->raw_data = devm_kzalloc(dev, OCC_DATA_MAX, GFP_KERNEL);
+ if (!driver->raw_data)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&driver->update_lock);
+
+ return driver;
+}
+EXPORT_SYMBOL(occ_start);
+
+MODULE_AUTHOR("Eddie James <eajames@xxxxxxxxxx>");
+MODULE_DESCRIPTION("OCC hwmon core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/occ.h b/drivers/hwmon/occ/occ.h
new file mode 100644
index 0000000..41f11b2
--- /dev/null
+++ b/drivers/hwmon/occ/occ.h
@@ -0,0 +1,80 @@
+/*
+ * occ.h - hwmon OCC driver
+ *
+ * This file contains data structures and function prototypes for
common access
+ * between different bus protocols and host systems.
+ *
+ * Copyright 2016 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __OCC_H__
+#define __OCC_H__
+
+#include "scom.h"
+
+struct device;
+struct occ;
+
+/* sensor_data_block_header
+ * structure to match the raw occ sensor block header
+ */
+struct sensor_data_block_header {
+ u8 sensor_type[4];
+ u8 reserved0;
+ u8 sensor_format;
+ u8 sensor_length;
+ u8 num_sensors;
+} __attribute__((packed, aligned(4)));
+
+struct sensor_data_block {
+ struct sensor_data_block_header header;
+ void *sensors;
+};
+
+enum sensor_type {
+ FREQ = 0,
+ TEMP,
+ POWER,
+ CAPS,
+ MAX_OCC_SENSOR_TYPE
+};
+
+struct occ_ops {
+ void (*parse_sensor)(u8 *data, void *sensor, int sensor_type, int
off,
+ int snum);
+ void *(*alloc_sensor)(int sensor_type, int num_sensors);
+ int (*get_sensor)(struct occ *driver, int sensor_type, int
sensor_num,
+ u32 hwmon, long *val);
+};
+
+struct occ_config {
+ u32 command_addr;
+ u32 response_addr;
+};
+
+struct occ_blocks {
+ int sensor_block_id[MAX_OCC_SENSOR_TYPE];
+ struct sensor_data_block blocks[MAX_OCC_SENSOR_TYPE];
+};
+
+struct occ *occ_start(struct device *dev, void *bus,
+ struct occ_bus_ops *bus_ops, const struct occ_ops *ops,
+ const struct occ_config *config);
+void *occ_get_sensor(struct occ *occ, int sensor_type);
+int occ_get_sensor_field(struct occ *occ, int sensor_type, int
sensor_num,
+ u32 hwmon, long *val);
+void occ_get_response_blocks(struct occ *occ, struct occ_blocks
**blocks);
+int occ_update_device(struct occ *driver);
+int occ_set_user_powercap(struct occ *occ, u16 cap);
+
+#endif /* __OCC_H__ */
diff --git a/drivers/hwmon/occ/scom.h b/drivers/hwmon/occ/scom.h
new file mode 100644
index 0000000..b0691f3
--- /dev/null
+++ b/drivers/hwmon/occ/scom.h
@@ -0,0 +1,47 @@
+/*
+ * scom.h - hwmon OCC driver
+ *
+ * This file contains data structures for scom operations to the OCC
+ *
+ * Copyright 2016 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SCOM_H__
+#define __SCOM_H__
+
+/*
+ * occ_bus_ops - represent the low-level transfer methods to
communicate with
+ * the OCC.
+ *
+ * getscom - OCC scom read
+ * @bus: handle to slave device
+ * @address: address
+ * @data: where to store data read from slave; buffer size must be at
least
+ * eight bytes.
+ *
+ * Returns 0 on success or a negative errno on error
+ *
+ * putscom - OCC scom write
+ * @bus: handle to slave device
+ * @address: address
+ * @data0: first data word to write
+ * @data1: second data word to write
+ *
+ * Returns 0 on success or a negative errno on error
+ */
+struct occ_bus_ops {
+ int (*getscom)(void *bus, u32 address, u64 *data);
+ int (*putscom)(void *bus, u32 address, u32 data0, u32 data1);
+};
+
+#endif /* __SCOM_H__ */