Message ID | 1485472758-11003-2-git-send-email-eajames.ibm@gmail.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Hi Edward, [auto build test ERROR on hwmon/hwmon-next] [also build test ERROR on v4.10-rc5 next-20170125] [cannot apply to linux/master] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] url: https://github.com/0day-ci/linux/commits/eajames-ibm-gmail-com/drivers-hwmon-Add-On-Chip-Controller-drive/20170127-073116 base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next config: alpha-allyesconfig (attached as .config) compiler: alpha-linux-gnu-gcc (Debian 6.1.1-9) 6.1.1 20160705 reproduce: wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # save the attached .config to linux build tree make.cross ARCH=alpha All error/warnings (new ones prefixed by >>): In file included from include/linux/linkage.h:6:0, from include/linux/kernel.h:6, from include/linux/unaligned/packed_struct.h:4, from include/linux/unaligned/le_struct.h:4, from arch/alpha/include/asm/unaligned.h:4, from drivers/hwmon/occ/occ.c:19: >> drivers/hwmon/occ/occ.c:433:15: error: 'occ_get_sensor_value' undeclared here (not in a function) EXPORT_SYMBOL(occ_get_sensor_value); ^ include/linux/export.h:58:16: note: in definition of macro '___EXPORT_SYMBOL' extern typeof(sym) sym; \ ^~~ >> drivers/hwmon/occ/occ.c:433:1: note: in expansion of macro 'EXPORT_SYMBOL' EXPORT_SYMBOL(occ_get_sensor_value); ^~~~~~~~~~~~~ vim +/occ_get_sensor_value +433 drivers/hwmon/occ/occ.c 427 428 int occ_get_sensor_field(struct occ *occ, int sensor_type, int sensor_num, 429 u32 hwmon, long *val) 430 { 431 return occ->ops.get_sensor(occ, sensor_type, sensor_num, hwmon, val); 432 } > 433 EXPORT_SYMBOL(occ_get_sensor_value); 434 435 void occ_get_response_blocks(struct occ *occ, struct occ_blocks **blocks) 436 { --- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Edward, [auto build test ERROR on hwmon/hwmon-next] [also build test ERROR on v4.10-rc5 next-20170125] [cannot apply to linux/master] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] url: https://github.com/0day-ci/linux/commits/eajames-ibm-gmail-com/drivers-hwmon-Add-On-Chip-Controller-drive/20170127-073116 base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next config: openrisc-allmodconfig (attached as .config) compiler: or32-linux-gcc (GCC) 4.5.1-or32-1.0rc1 reproduce: wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # save the attached .config to linux build tree make.cross ARCH=openrisc All errors (new ones prefixed by >>): drivers/hwmon/occ/occ.c:433:1: error: 'occ_get_sensor_value' undeclared here (not in a function) >> drivers/hwmon/occ/occ.c:433:1: error: type defaults to 'int' in declaration of 'occ_get_sensor_value' vim +433 drivers/hwmon/occ/occ.c 427 428 int occ_get_sensor_field(struct occ *occ, int sensor_type, int sensor_num, 429 u32 hwmon, long *val) 430 { 431 return occ->ops.get_sensor(occ, sensor_type, sensor_num, hwmon, val); 432 } > 433 EXPORT_SYMBOL(occ_get_sensor_value); 434 435 void occ_get_response_blocks(struct occ *occ, struct occ_blocks **blocks) 436 { --- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
On 01/26/2017 03:19 PM, eajames.ibm@gmail.com wrote: > From: "Edward A. James" <eajames@us.ibm.com> > > Add core support for polling the OCC for it's sensor data and parsing that > data into sensor-specific information. > > Signed-off-by: Edward A. James <eajames@us.ibm.com> > Signed-off-by: Andrew Jeffery <andrew@aj.id.au> > --- > Documentation/hwmon/occ | 40 ++++ > MAINTAINERS | 7 + > drivers/hwmon/Kconfig | 2 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/occ/Kconfig | 15 ++ > drivers/hwmon/occ/Makefile | 1 + > drivers/hwmon/occ/occ.c | 479 +++++++++++++++++++++++++++++++++++++++++++++ > drivers/hwmon/occ/occ.h | 80 ++++++++ > drivers/hwmon/occ/scom.h | 47 +++++ > 9 files changed, 672 insertions(+) > 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/occ.c > create mode 100644 drivers/hwmon/occ/occ.h > create mode 100644 drivers/hwmon/occ/scom.h > > diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ > new file mode 100644 > index 0000000..79d1642 > --- /dev/null > +++ b/Documentation/hwmon/occ > @@ -0,0 +1,40 @@ > +Kernel driver occ > +================= > + > +Supported chips: > + * ASPEED AST2400 > + * ASPEED AST2500 > + > +Please note that the chip must be connected to a POWER8 or POWER9 processor > +(see the BMC - Host Communications section). > + > +Author: Eddie James <eajames@us.ibm.com> > + > +Description > +----------- > + > +This driver implements support for the OCC (On-Chip Controller) on the IBM > +POWER8 and POWER9 processors, from a BMC (Baseboard Management Controller). The > +OCC is an embedded processor that provides real time power and thermal > +monitoring. > + > +This driver provides an interface on a BMC to poll OCC sensor data, set user > +power caps, and perform some basic OCC error handling. > + > +Currently, all versions of the OCC support four types of sensor data: power, > +temperature, frequency, and "caps," which indicate limits and thresholds used > +internally on the OCC. > + > +BMC - Host Communications > +------------------------- > + > +For the POWER8 application, the BMC can communicate with the P8 over I2C bus. > +However, to access the OCC register space, any data transfer must use a SCOM > +operation. SCOM is a procedure to initiate a data transfer, typically of 8 > +bytes. SCOMs consist of writing a 32-bit command register and then > +reading/writing two 32-bit data registers. This driver implements these > +SCOM operations over I2C bus in order to communicate with the OCC. > + > +For the POWER9 application, the BMC can communicate with the P9 over FSI bus > +and SBE engine. Once again, SCOM operations are required. This driver will > +implement SCOM ops over FSI/SBE. This will require the FSI driver. > diff --git a/MAINTAINERS b/MAINTAINERS > index 5f0420a..f5d4195 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -9112,6 +9112,13 @@ T: git git://linuxtv.org/media_tree.git > S: Maintained > F: drivers/media/i2c/ov7670.c > > +ON-CHIP CONTROLLER HWMON DRIVER > +M: Eddie James <eajames@us.ibm.com> > +L: linux-hwmon@vger.kernel.org > +S: Maintained > +F: Documentation/hwmon/occ > +F: drivers/hwmon/occ/ > + > ONENAND FLASH DRIVER > M: Kyungmin Park <kyungmin.park@samsung.com> > L: linux-mtd@lists.infradead.org > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index 190d270..e80ca81 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -1240,6 +1240,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 d2cb7e8..c7ec5d4 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -169,6 +169,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_PPC_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..cdb64a7 > --- /dev/null > +++ b/drivers/hwmon/occ/Kconfig > @@ -0,0 +1,15 @@ > +# > +# On Chip Controller configuration > +# > + > +menuconfig SENSORS_PPC_OCC > + bool "PPC On-Chip Controller" > + help > + If you say yes here you get support to monitor Power CPU > + sensors via the On-Chip Controller (OCC). > + > + Generally this is used by management controllers such as a BMC > + on an OpenPower system. > + > + This driver can also be built as a module. If so, the module > + will be called occ. > diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile > new file mode 100644 > index 0000000..93cb52f > --- /dev/null > +++ b/drivers/hwmon/occ/Makefile > @@ -0,0 +1 @@ > +obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o > diff --git a/drivers/hwmon/occ/occ.c b/drivers/hwmon/occ/occ.c > new file mode 100644 > index 0000000..3b233d8 > --- /dev/null > +++ b/drivers/hwmon/occ/occ.c > @@ -0,0 +1,479 @@ > +/* > + * occ.c - OCC hwmon driver > + * > + * This file contains the methods and data structures for the OCC hwmon driver. > + * > + * 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. > + */ > + > +#include <asm/unaligned.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/init.h> > +#include <linux/jiffies.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/slab.h> > +#include "occ.h" > + > +#define OCC_DATA_MAX 4096 > +#define OCC_DATA_MIN 40 > +#define OCC_BMC_TIMEOUT_MS 20000 > + > +/* 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 > + > +/* To init OCB */ > +#define OCB_AND_INIT0 0xFBFFFFFF > +#define OCB_AND_INIT1 0xFFFFFFFF > +#define OCB_OR_INIT0 0x08000000 > +#define OCB_OR_INIT1 0x00000000 > + > +/* To generate attention on OCC */ > +#define ATTN0 0x01010000 > +#define ATTN1 0x00000000 > + > +/* OCC return status */ > +#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 > + > +/* time interval to retry on "command in progress" return status */ > +#define CMD_IN_PRG_INT_MS 100 > +#define CMD_IN_PRG_RETRIES (OCC_BMC_TIMEOUT_MS / CMD_IN_PRG_INT_MS) > + > +/* OCC command definitions */ > +#define OCC_POLL 0 > +#define OCC_SET_USER_POWR_CAP 0x22 > + > +/* OCC poll command data */ > +#define OCC_POLL_STAT_SENSOR 0x10 > + > +/* OCC response data offsets */ > +#define RESP_RETURN_STATUS 2 > +#define RESP_DATA_LENGTH 3 > +#define RESP_HEADER_OFFSET 5 > +#define SENSOR_STR_OFFSET 37 > +#define NUM_SENSOR_BLOCKS_OFFSET 43 > +#define SENSOR_BLOCK_OFFSET 45 > + > +/* occ_poll_header > + * structure to match the raw occ poll response data > + */ > +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 num_sensor_blocks; > + u8 sensor_data_version; > +} __attribute__((packed, aligned(4))); > + > +struct occ_response { > + struct occ_poll_header header; > + struct occ_blocks data; > +}; > + > +struct occ { > + struct device *dev; > + void *bus; > + struct occ_bus_ops bus_ops; > + struct occ_ops ops; > + struct occ_config config; > + unsigned long update_interval; > + unsigned long last_updated; > + struct mutex update_lock; > + struct occ_response response; > + bool valid; > + u8 *raw_data; > +}; > + > +static int occ_check_sensor(struct occ *driver, u8 sensor_length, > + u8 num_sensors, enum sensor_type t, int b) > +{ > + struct occ_response *resp = &driver->response; > + struct sensor_data_block *block = &resp->data.blocks[b]; > + > + if (block->sensors) { > + /* empty block, release old data */ > + if (num_sensors == 0 || sensor_length == 0) { > + devm_kfree(driver->dev, block->sensors); > + 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"); > + rc = -ENODATA; > + goto err; > + } > + > + num_sensor_blocks = data[NUM_SENSOR_BLOCKS_OFFSET]; > + if (num_sensor_blocks == 0) { > + dev_warn(dev, "no sensor blocks available\n"); > + rc = -ENODATA; > + goto err; > + } > + > + 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"); > + goto err; > + } > + > + 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) > + goto err; > + 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"); > + goto err; > + } > + > + driver->ops.parse_sensor(data, > + resp->data.blocks[b].sensors, > + sensor_type, offset, s); > + offset += block->sensor_length; > + } > + } > + > + return 0; > +err: > + return rc; Please no unnecessary gotos. The code jumping here can return directly. > +} > + > +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) > + goto out; > + > + 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); > + rc = -EINVAL; > + goto out; > + } > + > + /* 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); > + goto out; > + } > + } > + > + /* 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); > + > +out: Same as above - this label is unnecessary. > + return rc; > +} > + > +int occ_update_device(struct occ *driver) > +{ > + int rc = 0; > + > + mutex_lock(&driver->update_lock); > + > + if (time_after(jiffies, driver->last_updated + driver->update_interval) Can you make update_interval a constant ? > + || !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 NULL; NULL or ERR_PTR() ? > + > + type_id = driver->response.data.sensor_block_id[sensor_type]; > + if (type_id == -1) > + return NULL; > + > + 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_value); Ad 0day found, something got messed up here. Please fix and resubmit. > + > +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); > + > + driver->update_interval = HZ; > + mutex_init(&driver->update_lock); > + > + return driver; > +} > +EXPORT_SYMBOL(occ_start); > + > +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); > +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__ */ > -- To unsubscribe from this list: send the line "unsubscribe linux-hwmon" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ new file mode 100644 index 0000000..79d1642 --- /dev/null +++ b/Documentation/hwmon/occ @@ -0,0 +1,40 @@ +Kernel driver occ +================= + +Supported chips: + * ASPEED AST2400 + * ASPEED AST2500 + +Please note that the chip must be connected to a POWER8 or POWER9 processor +(see the BMC - Host Communications section). + +Author: Eddie James <eajames@us.ibm.com> + +Description +----------- + +This driver implements support for the OCC (On-Chip Controller) on the IBM +POWER8 and POWER9 processors, from a BMC (Baseboard Management Controller). The +OCC is an embedded processor that provides real time power and thermal +monitoring. + +This driver provides an interface on a BMC to poll OCC sensor data, set user +power caps, and perform some basic OCC error handling. + +Currently, all versions of the OCC support four types of sensor data: power, +temperature, frequency, and "caps," which indicate limits and thresholds used +internally on the OCC. + +BMC - Host Communications +------------------------- + +For the POWER8 application, the BMC can communicate with the P8 over I2C bus. +However, to access the OCC register space, any data transfer must use a SCOM +operation. SCOM is a procedure to initiate a data transfer, typically of 8 +bytes. SCOMs consist of writing a 32-bit command register and then +reading/writing two 32-bit data registers. This driver implements these +SCOM operations over I2C bus in order to communicate with the OCC. + +For the POWER9 application, the BMC can communicate with the P9 over FSI bus +and SBE engine. Once again, SCOM operations are required. This driver will +implement SCOM ops over FSI/SBE. This will require the FSI driver. diff --git a/MAINTAINERS b/MAINTAINERS index 5f0420a..f5d4195 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9112,6 +9112,13 @@ T: git git://linuxtv.org/media_tree.git S: Maintained F: drivers/media/i2c/ov7670.c +ON-CHIP CONTROLLER HWMON DRIVER +M: Eddie James <eajames@us.ibm.com> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/occ +F: drivers/hwmon/occ/ + ONENAND FLASH DRIVER M: Kyungmin Park <kyungmin.park@samsung.com> L: linux-mtd@lists.infradead.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 190d270..e80ca81 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1240,6 +1240,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 d2cb7e8..c7ec5d4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -169,6 +169,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_PPC_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..cdb64a7 --- /dev/null +++ b/drivers/hwmon/occ/Kconfig @@ -0,0 +1,15 @@ +# +# On Chip Controller configuration +# + +menuconfig SENSORS_PPC_OCC + bool "PPC On-Chip Controller" + help + If you say yes here you get support to monitor Power CPU + sensors via the On-Chip Controller (OCC). + + Generally this is used by management controllers such as a BMC + on an OpenPower system. + + This driver can also be built as a module. If so, the module + will be called occ. diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile new file mode 100644 index 0000000..93cb52f --- /dev/null +++ b/drivers/hwmon/occ/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SENSORS_PPC_OCC) += occ.o diff --git a/drivers/hwmon/occ/occ.c b/drivers/hwmon/occ/occ.c new file mode 100644 index 0000000..3b233d8 --- /dev/null +++ b/drivers/hwmon/occ/occ.c @@ -0,0 +1,479 @@ +/* + * occ.c - OCC hwmon driver + * + * This file contains the methods and data structures for the OCC hwmon driver. + * + * 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. + */ + +#include <asm/unaligned.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include "occ.h" + +#define OCC_DATA_MAX 4096 +#define OCC_DATA_MIN 40 +#define OCC_BMC_TIMEOUT_MS 20000 + +/* 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 + +/* To init OCB */ +#define OCB_AND_INIT0 0xFBFFFFFF +#define OCB_AND_INIT1 0xFFFFFFFF +#define OCB_OR_INIT0 0x08000000 +#define OCB_OR_INIT1 0x00000000 + +/* To generate attention on OCC */ +#define ATTN0 0x01010000 +#define ATTN1 0x00000000 + +/* OCC return status */ +#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 + +/* time interval to retry on "command in progress" return status */ +#define CMD_IN_PRG_INT_MS 100 +#define CMD_IN_PRG_RETRIES (OCC_BMC_TIMEOUT_MS / CMD_IN_PRG_INT_MS) + +/* OCC command definitions */ +#define OCC_POLL 0 +#define OCC_SET_USER_POWR_CAP 0x22 + +/* OCC poll command data */ +#define OCC_POLL_STAT_SENSOR 0x10 + +/* OCC response data offsets */ +#define RESP_RETURN_STATUS 2 +#define RESP_DATA_LENGTH 3 +#define RESP_HEADER_OFFSET 5 +#define SENSOR_STR_OFFSET 37 +#define NUM_SENSOR_BLOCKS_OFFSET 43 +#define SENSOR_BLOCK_OFFSET 45 + +/* occ_poll_header + * structure to match the raw occ poll response data + */ +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 num_sensor_blocks; + u8 sensor_data_version; +} __attribute__((packed, aligned(4))); + +struct occ_response { + struct occ_poll_header header; + struct occ_blocks data; +}; + +struct occ { + struct device *dev; + void *bus; + struct occ_bus_ops bus_ops; + struct occ_ops ops; + struct occ_config config; + unsigned long update_interval; + unsigned long last_updated; + struct mutex update_lock; + struct occ_response response; + bool valid; + u8 *raw_data; +}; + +static int occ_check_sensor(struct occ *driver, u8 sensor_length, + u8 num_sensors, enum sensor_type t, int b) +{ + struct occ_response *resp = &driver->response; + struct sensor_data_block *block = &resp->data.blocks[b]; + + if (block->sensors) { + /* empty block, release old data */ + if (num_sensors == 0 || sensor_length == 0) { + devm_kfree(driver->dev, block->sensors); + 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"); + rc = -ENODATA; + goto err; + } + + num_sensor_blocks = data[NUM_SENSOR_BLOCKS_OFFSET]; + if (num_sensor_blocks == 0) { + dev_warn(dev, "no sensor blocks available\n"); + rc = -ENODATA; + goto err; + } + + 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"); + goto err; + } + + 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) + goto err; + 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"); + goto err; + } + + driver->ops.parse_sensor(data, + resp->data.blocks[b].sensors, + sensor_type, offset, s); + offset += block->sensor_length; + } + } + + return 0; +err: + return rc; +} + +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) + goto out; + + 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); + rc = -EINVAL; + goto out; + } + + /* 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); + goto out; + } + } + + /* 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); + +out: + return rc; +} + +int occ_update_device(struct occ *driver) +{ + int rc = 0; + + mutex_lock(&driver->update_lock); + + if (time_after(jiffies, driver->last_updated + driver->update_interval) + || !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 NULL; + + type_id = driver->response.data.sensor_block_id[sensor_type]; + if (type_id == -1) + return NULL; + + 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_value); + +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); + + driver->update_interval = HZ; + mutex_init(&driver->update_lock); + + return driver; +} +EXPORT_SYMBOL(occ_start); + +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); +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__ */