hwmon: Add OCC driver basic sysfs attributes
RFC v4 3/6 hwmon: Add OCC driver basic sysfs attributes
From: "Edward A. James" <[email protected]>
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 <[email protected]>
--- drivers/hwmon/occ/power8_occ.c | 178 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 1 deletion(-)
drivers/hwmon/occ/power8_occ.c +177 -1
@@ -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); }
Signature · 1 line
1.9.1