From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754087AbdFVWte (ORCPT ); Thu, 22 Jun 2017 18:49:34 -0400 Received: from mx0a-001b2d01.pphosted.com ([148.163.156.1]:58219 "EHLO mx0a-001b2d01.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753582AbdFVWtB (ORCPT ); Thu, 22 Jun 2017 18:49:01 -0400 From: Eddie James To: linux-kernel@vger.kernel.org Cc: linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux@roeck-us.net, jdelvare@suse.com, mark.rutland@arm.com, robh+dt@kernel.org, gregkh@linuxfoundation.org, cbostic@linux.vnet.ibm.com, jk@ozlabs.org, joel@jms.id.au, andrew@aj.id.au, eajames@linux.vnet.ibm.com, "Edward A. James" Subject: [PATCH 5/7] drivers/hwmon/occ: Add sensor attributes and register hwmon device Date: Thu, 22 Jun 2017 17:48:34 -0500 X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1498171716-26620-1-git-send-email-eajames@linux.vnet.ibm.com> References: <1498171716-26620-1-git-send-email-eajames@linux.vnet.ibm.com> X-TM-AS-GCONF: 00 x-cbid: 17062222-8235-0000-0000-00000BC73D81 X-IBM-SpamModules-Scores: X-IBM-SpamModules-Versions: BY=3.00007274; HX=3.00000241; KW=3.00000007; PH=3.00000004; SC=3.00000214; SDB=6.00878480; UDB=6.00437728; IPR=6.00658620; BA=6.00005437; NDR=6.00000001; ZLA=6.00000005; ZF=6.00000009; ZB=6.00000000; ZP=6.00000000; ZH=6.00000000; ZU=6.00000002; MB=3.00015928; XFM=3.00000015; UTC=2017-06-22 22:48:54 X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 17062222-8236-0000-0000-00003C69264C Message-Id: <1498171716-26620-6-git-send-email-eajames@linux.vnet.ibm.com> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:,, definitions=2017-06-22_09:,, signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1703280000 definitions=main-1706220389 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: "Edward A. James" Setup the sensor attributes for every OCC sensor found by the first poll response. Register the attributes with hwmon. Add hwmon documentation for the driver. Signed-off-by: Edward A. James --- Documentation/hwmon/occ | 84 ++++++++++ drivers/hwmon/occ/common.c | 395 +++++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/occ/common.h | 14 ++ 3 files changed, 493 insertions(+) create mode 100644 Documentation/hwmon/occ diff --git a/Documentation/hwmon/occ b/Documentation/hwmon/occ new file mode 100644 index 0000000..85173f2 --- /dev/null +++ b/Documentation/hwmon/occ @@ -0,0 +1,84 @@ +Kernel driver occ-hwmon +======================= + +Supported chips: + * POWER8 + * POWER9 + +Author: Eddie James + +Description +----------- + +This driver supports hardware monitoring for the On-Chip Controller (OCC) +embedded on POWER processors. The OCC is a device that collects and aggregates +sensor data from the processor and the system. The OCC can provide the raw +sensor data as well as perform thermal and power management on the system. + +The P8 version of this driver is a client driver of I2C. It will be probed +automatically if an "ibm,p8-occ-hwmon" compatible device is found under the +appropriate I2C bus node in the device-tree. + +The P9 version of this driver is a client driver of the FSI-based OCC driver. +It will be probed automatically if an "ibm,p9-occ-hwmon" compatible device is +found under the appropriate OCC device node in the device-tree. However, due +to the way FSI bus probes it's client device drivers, the probe during FSI +scan may fail by design. In that case, the P9 OCC hwmon driver must be +instantiated explicitly. + +Sysfs entries +------------- + +The following attributes are supported. All attributes are read-only unless +specified. + +temp[1-n]_label OCC sensor id. +temp[1-n]_input Measured temperature in millidegrees C. +[with temperature sensor version 2+] + temp[1-n]_fru_type Given FRU (Field Replaceable Unit) type. + +freq[1-n]_label OCC sensor id. +freq[1-n]_input Measured frequency. + +power[1-n]_label OCC sensor id. +power[1-n]_input Measured power in watts. +power[1-n]_update_tag Number of 250us samples represented in accumulator. +power[1-n]_accumulator Accumulation of 250us power readings. +[with power sensor version 2+] + power[1-n]_function_id Identifies what the power reading is for. + power[1-n]_apss_channel Indicates APSS channel. + +[power version 0xa0 only] +power1_label OCC sensor id. +power1_system_update_time Time in us that the system power is read. +power1_system_input Most recent system power reading in watts. +power1_system_update_tag Number of samples in system accumulator. +power1_system_accumulator Accumulation of system power readings. +power1_proc_update_time Time in us that the processor power is read. +power1_proc_input Most recent processor power reading in watts. +power1_proc_update_tag Number of samples in processor accumulator. +power1_proc_accumulator Accumulation of processor power readings. +power1_vdd_input Most recent proc Vdd power reading in watts. +power1_vdd_update_tag Number of samples in processor Vdd accumulator. +power1_vdd_accumulator Accumulation of processor Vdd power readings. +power1_vdn_input Most recent proc Vdn power reading in watts. +power1_vdn_update_tag Number of samples in processor Vdn accumulator. +power1_vnd_accumulator Accumulation of processor Vdn power readings. + +caps1_current Current OCC power cap in watts. +caps1_reading Current system output power in watts. +caps1_norm Power cap without redundant power. +caps1_max Maximum power cap. +[caps version 1 and 2 only] + caps1_min Minimum power cap. +[caps version 3+] + caps1_min_hard Hard minimum cap that can be set and held. + caps1_min_soft Soft minimum cap below hard, not guaranteed. +caps1_user The powercap specified by the user. Will be 0 if no + user powercap exists. This attribute is read-write. +[caps version 1+] + caps1_user_source Indicates how the user power limit was set. + +extn[1-n]_label ASCII id or sensor id. +extn[1-n]_flags Indicates type of label attribute. +extn[1-n]_input Data. diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index b55813a3..eb1f1a1 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -615,6 +615,384 @@ static ssize_t occ_show_extended(struct device *dev, return rc; } +/* Some helper macros to make it easier to define an occ_attribute. Since these + * are dynamically allocated, we shouldn't use the existing kernel macros which + * stringify the name argument. + */ +#define ATTR_OCC(_name, _mode, _show, _store) { \ + .attr = { \ + .name = _name, \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode), \ + }, \ + .show = _show, \ + .store = _store, \ +} + +#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) { \ + .dev_attr = ATTR_OCC(_name, _mode, _show, _store), \ + .index = _index, \ + .nr = _nr, \ +} + +#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index) \ + ((struct sensor_device_attribute_2) \ + SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index)) + +/* Allocate and instatiate sensor_device_attribute_2s. It's most efficient to + * use our own instead of the built-in hwmon attribute types. + */ +static int occ_setup_sensor_attrs(struct occ *occ) +{ + unsigned int i, s; + struct device *dev = occ->bus_dev; + struct occ_sensors *sensors = &occ->sensors; + struct occ_attribute *attr; + ssize_t (*show_temp)(struct device *, struct device_attribute *, + char *) = occ_show_temp_1; + ssize_t (*show_freq)(struct device *, struct device_attribute *, + char *) = occ_show_freq_1; + ssize_t (*show_power)(struct device *, struct device_attribute *, + char *) = occ_show_power_1; + ssize_t (*show_caps)(struct device *, struct device_attribute *, + char *) = occ_show_caps_1; + + occ->num_attrs = 0; + + switch (sensors->temp.version) { + case 1: + occ->num_attrs += (sensors->temp.num_sensors * 2); + break; + case 2: + occ->num_attrs += (sensors->temp.num_sensors * 3); + show_temp = occ_show_temp_2; + break; + default: + sensors->temp.num_sensors = 0; + } + + switch (sensors->freq.version) { + case 2: + show_freq = occ_show_freq_2; + /* fall through */ + case 1: + occ->num_attrs += (sensors->freq.num_sensors * 2); + break; + default: + sensors->freq.num_sensors = 0; + } + + switch (sensors->power.version) { + case 1: + occ->num_attrs += (sensors->power.num_sensors * 4); + break; + case 2: + occ->num_attrs += (sensors->power.num_sensors * 6); + show_power = occ_show_power_2; + break; + case 0xA0: + occ->num_attrs += (sensors->power.num_sensors * 15); + show_power = occ_show_power_a0; + break; + default: + sensors->power.num_sensors = 0; + } + + switch (sensors->caps.version) { + case 1: + occ->num_attrs += (sensors->caps.num_sensors * 6); + break; + case 2: + occ->num_attrs += (sensors->caps.num_sensors * 7); + show_caps = occ_show_caps_2; + break; + case 3: + occ->num_attrs += (sensors->caps.num_sensors * 8); + show_caps = occ_show_caps_3; + break; + default: + sensors->caps.num_sensors = 0; + } + + switch (sensors->extended.version) { + case 1: + occ->num_attrs += sensors->extended.num_sensors; + break; + default: + sensors->extended.num_sensors = 0; + } + + occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * occ->num_attrs, + GFP_KERNEL); + if (!occ->attrs) + return -ENOMEM; + + /* null-terminated list */ + occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) * + occ->num_attrs + 1, GFP_KERNEL); + if (!occ->group.attrs) + return -ENOMEM; + + attr = occ->attrs; + + for (i = 0; i < sensors->temp.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "temp%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL, + 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "temp%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL, + 1, i); + attr++; + + if (sensors->temp.version > 1) { + snprintf(attr->name, sizeof(attr->name), + "temp%d_fru_type", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_temp, NULL, 2, i); + attr++; + } + } + + for (i = 0; i < sensors->freq.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "freq%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL, + 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "freq%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL, + 1, i); + attr++; + } + + if (sensors->power.version == 0xA0) { + for (i = 0; i < sensors->power.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_system_update_time", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_system_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_system_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 3, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_system_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 4, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_proc_update_time", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 5, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_proc_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 6, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_proc_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 7, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_proc_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 8, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_vdd_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 9, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_vdd_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 10, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_vdd_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 11, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_vdn_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 12, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_vdn_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 13, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_vdn_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 14, i); + attr++; + } + } else { + for (i = 0; i < sensors->power.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 3, i); + attr++; + + if (sensors->power.version > 1) { + snprintf(attr->name, sizeof(attr->name), + "power%d_function_id", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, + 4, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_apss_channel", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, + 5, i); + attr++; + } + } + } + + for (i = 0; i < sensors->caps.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "caps%d_current", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "caps%d_reading", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "caps%d_norm", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "caps%d_max", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 3, i); + attr++; + + if (sensors->caps.version > 2) { + snprintf(attr->name, sizeof(attr->name), + "caps%d_min_hard", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 4, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "caps%d_min_soft", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 7, i); + attr++; + } else { + snprintf(attr->name, sizeof(attr->name), "caps%d_min", + s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 4, i); + attr++; + } + + snprintf(attr->name, sizeof(attr->name), "caps%d_user", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps, + occ_store_caps_user, 5, i); + attr++; + + if (sensors->caps.version > 1) { + snprintf(attr->name, sizeof(attr->name), + "caps%d_user_source", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 6, i); + attr++; + } + } + + for (i = 0; i < sensors->extended.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "extn%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + occ_show_extended, NULL, 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "extn%d_flags", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + occ_show_extended, NULL, 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "extn%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + occ_show_extended, NULL, 2, i); + attr++; + } + + /* put the sensors in the group */ + for (i = 0; i < occ->num_attrs; ++i) + occ->group.attrs[i] = &occ->attrs[i].sensor.dev_attr.attr; + + return 0; +} + /* only need to do this once at startup, as OCC won't change sensors on us */ static void occ_parse_poll_response(struct occ *occ) { @@ -667,6 +1045,7 @@ int occ_setup(struct occ *occ, const char *name) int rc; mutex_init(&occ->lock); + occ->groups[0] = &occ->group; /* no need to lock */ rc = occ_poll(occ); @@ -678,5 +1057,21 @@ int occ_setup(struct occ *occ, const char *name) occ_parse_poll_response(occ); + rc = occ_setup_sensor_attrs(occ); + if (rc) { + dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n", + rc); + return rc; + } + + occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name, + occ, occ->groups); + if (IS_ERR(occ->hwmon)) { + rc = PTR_ERR(occ->hwmon); + dev_err(occ->bus_dev, "failed to register hwmon device: %d\n", + rc); + return rc; + } + return 0; } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index f4e1df0..4bcf3ca 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -94,6 +94,14 @@ struct occ_sensors { struct occ_sensor extended; }; +/* Use our own attribute struct so we can dynamically allocate space for the + * name. + */ +struct occ_attribute { + char name[32]; + struct sensor_device_attribute_2 sensor; +}; + struct occ { struct device *bus_dev; @@ -105,6 +113,12 @@ struct occ { unsigned long last_update; struct mutex lock; + + struct device *hwmon; + unsigned int num_attrs; + struct occ_attribute *attrs; + struct attribute_group group; + const struct attribute_group *groups[2]; }; int occ_setup(struct occ *occ, const char *name); -- 1.8.3.1