18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci// Copyright IBM Corp 2019 38c2ecf20Sopenharmony_ci 48c2ecf20Sopenharmony_ci#include <linux/bitops.h> 58c2ecf20Sopenharmony_ci#include <linux/device.h> 68c2ecf20Sopenharmony_ci#include <linux/export.h> 78c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include "common.h" 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* OCC status register */ 148c2ecf20Sopenharmony_ci#define OCC_STAT_MASTER BIT(7) 158c2ecf20Sopenharmony_ci#define OCC_STAT_ACTIVE BIT(0) 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci/* OCC extended status register */ 188c2ecf20Sopenharmony_ci#define OCC_EXT_STAT_DVFS_OT BIT(7) 198c2ecf20Sopenharmony_ci#define OCC_EXT_STAT_DVFS_POWER BIT(6) 208c2ecf20Sopenharmony_ci#define OCC_EXT_STAT_MEM_THROTTLE BIT(5) 218c2ecf20Sopenharmony_ci#define OCC_EXT_STAT_QUICK_DROP BIT(4) 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic ssize_t occ_sysfs_show(struct device *dev, 248c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci int rc; 278c2ecf20Sopenharmony_ci int val = 0; 288c2ecf20Sopenharmony_ci struct occ *occ = dev_get_drvdata(dev); 298c2ecf20Sopenharmony_ci struct occ_poll_response_header *header; 308c2ecf20Sopenharmony_ci struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci rc = occ_update_response(occ); 338c2ecf20Sopenharmony_ci if (rc) 348c2ecf20Sopenharmony_ci return rc; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci header = (struct occ_poll_response_header *)occ->resp.data; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci switch (sattr->index) { 398c2ecf20Sopenharmony_ci case 0: 408c2ecf20Sopenharmony_ci val = !!(header->status & OCC_STAT_MASTER); 418c2ecf20Sopenharmony_ci break; 428c2ecf20Sopenharmony_ci case 1: 438c2ecf20Sopenharmony_ci val = !!(header->status & OCC_STAT_ACTIVE); 448c2ecf20Sopenharmony_ci break; 458c2ecf20Sopenharmony_ci case 2: 468c2ecf20Sopenharmony_ci val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); 478c2ecf20Sopenharmony_ci break; 488c2ecf20Sopenharmony_ci case 3: 498c2ecf20Sopenharmony_ci val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); 508c2ecf20Sopenharmony_ci break; 518c2ecf20Sopenharmony_ci case 4: 528c2ecf20Sopenharmony_ci val = !!(header->ext_status & OCC_EXT_STAT_MEM_THROTTLE); 538c2ecf20Sopenharmony_ci break; 548c2ecf20Sopenharmony_ci case 5: 558c2ecf20Sopenharmony_ci val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); 568c2ecf20Sopenharmony_ci break; 578c2ecf20Sopenharmony_ci case 6: 588c2ecf20Sopenharmony_ci val = header->occ_state; 598c2ecf20Sopenharmony_ci break; 608c2ecf20Sopenharmony_ci case 7: 618c2ecf20Sopenharmony_ci if (header->status & OCC_STAT_MASTER) 628c2ecf20Sopenharmony_ci val = hweight8(header->occs_present); 638c2ecf20Sopenharmony_ci else 648c2ecf20Sopenharmony_ci val = 1; 658c2ecf20Sopenharmony_ci break; 668c2ecf20Sopenharmony_ci default: 678c2ecf20Sopenharmony_ci return -EINVAL; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic ssize_t occ_error_show(struct device *dev, 748c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci struct occ *occ = dev_get_drvdata(dev); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci occ_update_response(occ); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE - 1, "%d\n", occ->error); 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0); 848c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_active, 0444, occ_sysfs_show, NULL, 1); 858c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2); 868c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_sysfs_show, NULL, 3); 878c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); 888c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5); 898c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); 908c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); 918c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(occ_error); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic struct attribute *occ_attributes[] = { 948c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_master.dev_attr.attr, 958c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_active.dev_attr.attr, 968c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr, 978c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_dvfs_power.dev_attr.attr, 988c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_mem_throttle.dev_attr.attr, 998c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr, 1008c2ecf20Sopenharmony_ci &sensor_dev_attr_occ_state.dev_attr.attr, 1018c2ecf20Sopenharmony_ci &sensor_dev_attr_occs_present.dev_attr.attr, 1028c2ecf20Sopenharmony_ci &dev_attr_occ_error.attr, 1038c2ecf20Sopenharmony_ci NULL 1048c2ecf20Sopenharmony_ci}; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic const struct attribute_group occ_sysfs = { 1078c2ecf20Sopenharmony_ci .attrs = occ_attributes, 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_civoid occ_sysfs_poll_done(struct occ *occ) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci const char *name; 1138c2ecf20Sopenharmony_ci struct occ_poll_response_header *header = 1148c2ecf20Sopenharmony_ci (struct occ_poll_response_header *)occ->resp.data; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci /* 1178c2ecf20Sopenharmony_ci * On the first poll response, we haven't yet created the sysfs 1188c2ecf20Sopenharmony_ci * attributes, so don't make any notify calls. 1198c2ecf20Sopenharmony_ci */ 1208c2ecf20Sopenharmony_ci if (!occ->hwmon) 1218c2ecf20Sopenharmony_ci goto done; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if ((header->status & OCC_STAT_MASTER) != 1248c2ecf20Sopenharmony_ci (occ->prev_stat & OCC_STAT_MASTER)) { 1258c2ecf20Sopenharmony_ci name = sensor_dev_attr_occ_master.dev_attr.attr.name; 1268c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if ((header->status & OCC_STAT_ACTIVE) != 1308c2ecf20Sopenharmony_ci (occ->prev_stat & OCC_STAT_ACTIVE)) { 1318c2ecf20Sopenharmony_ci name = sensor_dev_attr_occ_active.dev_attr.attr.name; 1328c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) != 1368c2ecf20Sopenharmony_ci (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_OT)) { 1378c2ecf20Sopenharmony_ci name = sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr.name; 1388c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if ((header->ext_status & OCC_EXT_STAT_DVFS_POWER) != 1428c2ecf20Sopenharmony_ci (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_POWER)) { 1438c2ecf20Sopenharmony_ci name = sensor_dev_attr_occ_dvfs_power.dev_attr.attr.name; 1448c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if ((header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) != 1488c2ecf20Sopenharmony_ci (occ->prev_ext_stat & OCC_EXT_STAT_MEM_THROTTLE)) { 1498c2ecf20Sopenharmony_ci name = sensor_dev_attr_occ_mem_throttle.dev_attr.attr.name; 1508c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if ((header->ext_status & OCC_EXT_STAT_QUICK_DROP) != 1548c2ecf20Sopenharmony_ci (occ->prev_ext_stat & OCC_EXT_STAT_QUICK_DROP)) { 1558c2ecf20Sopenharmony_ci name = sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr.name; 1568c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if ((header->status & OCC_STAT_MASTER) && 1608c2ecf20Sopenharmony_ci header->occs_present != occ->prev_occs_present) { 1618c2ecf20Sopenharmony_ci name = sensor_dev_attr_occs_present.dev_attr.attr.name; 1628c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (occ->error && occ->error != occ->prev_error) { 1668c2ecf20Sopenharmony_ci name = dev_attr_occ_error.attr.name; 1678c2ecf20Sopenharmony_ci sysfs_notify(&occ->bus_dev->kobj, NULL, name); 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci /* no notifications for OCC state; doesn't indicate error condition */ 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cidone: 1738c2ecf20Sopenharmony_ci occ->prev_error = occ->error; 1748c2ecf20Sopenharmony_ci occ->prev_stat = header->status; 1758c2ecf20Sopenharmony_ci occ->prev_ext_stat = header->ext_status; 1768c2ecf20Sopenharmony_ci occ->prev_occs_present = header->occs_present; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ciint occ_setup_sysfs(struct occ *occ) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci return sysfs_create_group(&occ->bus_dev->kobj, &occ_sysfs); 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_civoid occ_shutdown(struct occ *occ) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci sysfs_remove_group(&occ->bus_dev->kobj, &occ_sysfs); 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(occ_shutdown); 189