162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * A hwmon driver for the IBM System Director Active Energy Manager (AEM) 462306a36Sopenharmony_ci * temperature/power/energy sensors and capping functionality. 562306a36Sopenharmony_ci * Copyright (C) 2008 IBM 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Darrick J. Wong <darrick.wong@oracle.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/ipmi.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/hwmon.h> 1562306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h> 1662306a36Sopenharmony_ci#include <linux/jiffies.h> 1762306a36Sopenharmony_ci#include <linux/mutex.h> 1862306a36Sopenharmony_ci#include <linux/kdev_t.h> 1962306a36Sopenharmony_ci#include <linux/spinlock.h> 2062306a36Sopenharmony_ci#include <linux/idr.h> 2162306a36Sopenharmony_ci#include <linux/slab.h> 2262306a36Sopenharmony_ci#include <linux/sched.h> 2362306a36Sopenharmony_ci#include <linux/platform_device.h> 2462306a36Sopenharmony_ci#include <linux/math64.h> 2562306a36Sopenharmony_ci#include <linux/time.h> 2662306a36Sopenharmony_ci#include <linux/err.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define REFRESH_INTERVAL (HZ) 2962306a36Sopenharmony_ci#define IPMI_TIMEOUT (30 * HZ) 3062306a36Sopenharmony_ci#define DRVNAME "aem" 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define AEM_NETFN 0x2E 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define AEM_FIND_FW_CMD 0x80 3562306a36Sopenharmony_ci#define AEM_ELEMENT_CMD 0x81 3662306a36Sopenharmony_ci#define AEM_FW_INSTANCE_CMD 0x82 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define AEM_READ_ELEMENT_CFG 0x80 3962306a36Sopenharmony_ci#define AEM_READ_BUFFER 0x81 4062306a36Sopenharmony_ci#define AEM_READ_REGISTER 0x82 4162306a36Sopenharmony_ci#define AEM_WRITE_REGISTER 0x83 4262306a36Sopenharmony_ci#define AEM_SET_REG_MASK 0x84 4362306a36Sopenharmony_ci#define AEM_CLEAR_REG_MASK 0x85 4462306a36Sopenharmony_ci#define AEM_READ_ELEMENT_CFG2 0x86 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define AEM_CONTROL_ELEMENT 0 4762306a36Sopenharmony_ci#define AEM_ENERGY_ELEMENT 1 4862306a36Sopenharmony_ci#define AEM_CLOCK_ELEMENT 4 4962306a36Sopenharmony_ci#define AEM_POWER_CAP_ELEMENT 7 5062306a36Sopenharmony_ci#define AEM_EXHAUST_ELEMENT 9 5162306a36Sopenharmony_ci#define AEM_POWER_ELEMENT 10 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#define AEM_MODULE_TYPE_ID 0x0001 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci#define AEM2_NUM_ENERGY_REGS 2 5662306a36Sopenharmony_ci#define AEM2_NUM_PCAP_REGS 6 5762306a36Sopenharmony_ci#define AEM2_NUM_TEMP_REGS 2 5862306a36Sopenharmony_ci#define AEM2_NUM_SENSORS 14 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci#define AEM1_NUM_ENERGY_REGS 1 6162306a36Sopenharmony_ci#define AEM1_NUM_SENSORS 3 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/* AEM 2.x has more energy registers */ 6462306a36Sopenharmony_ci#define AEM_NUM_ENERGY_REGS AEM2_NUM_ENERGY_REGS 6562306a36Sopenharmony_ci/* AEM 2.x needs more sensor files */ 6662306a36Sopenharmony_ci#define AEM_NUM_SENSORS AEM2_NUM_SENSORS 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci#define POWER_CAP 0 6962306a36Sopenharmony_ci#define POWER_CAP_MAX_HOTPLUG 1 7062306a36Sopenharmony_ci#define POWER_CAP_MAX 2 7162306a36Sopenharmony_ci#define POWER_CAP_MIN_WARNING 3 7262306a36Sopenharmony_ci#define POWER_CAP_MIN 4 7362306a36Sopenharmony_ci#define POWER_AUX 5 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci#define AEM_DEFAULT_POWER_INTERVAL 1000 7662306a36Sopenharmony_ci#define AEM_MIN_POWER_INTERVAL 200 7762306a36Sopenharmony_ci#define UJ_PER_MJ 1000L 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic DEFINE_IDA(aem_ida); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic struct platform_driver aem_driver = { 8262306a36Sopenharmony_ci .driver = { 8362306a36Sopenharmony_ci .name = DRVNAME, 8462306a36Sopenharmony_ci .bus = &platform_bus_type, 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistruct aem_ipmi_data { 8962306a36Sopenharmony_ci struct completion read_complete; 9062306a36Sopenharmony_ci struct ipmi_addr address; 9162306a36Sopenharmony_ci struct ipmi_user *user; 9262306a36Sopenharmony_ci int interface; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci struct kernel_ipmi_msg tx_message; 9562306a36Sopenharmony_ci long tx_msgid; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci void *rx_msg_data; 9862306a36Sopenharmony_ci unsigned short rx_msg_len; 9962306a36Sopenharmony_ci unsigned char rx_result; 10062306a36Sopenharmony_ci int rx_recv_type; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci struct device *bmc_device; 10362306a36Sopenharmony_ci}; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistruct aem_ro_sensor_template { 10662306a36Sopenharmony_ci char *label; 10762306a36Sopenharmony_ci ssize_t (*show)(struct device *dev, 10862306a36Sopenharmony_ci struct device_attribute *devattr, 10962306a36Sopenharmony_ci char *buf); 11062306a36Sopenharmony_ci int index; 11162306a36Sopenharmony_ci}; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistruct aem_rw_sensor_template { 11462306a36Sopenharmony_ci char *label; 11562306a36Sopenharmony_ci ssize_t (*show)(struct device *dev, 11662306a36Sopenharmony_ci struct device_attribute *devattr, 11762306a36Sopenharmony_ci char *buf); 11862306a36Sopenharmony_ci ssize_t (*set)(struct device *dev, 11962306a36Sopenharmony_ci struct device_attribute *devattr, 12062306a36Sopenharmony_ci const char *buf, size_t count); 12162306a36Sopenharmony_ci int index; 12262306a36Sopenharmony_ci}; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistruct aem_data { 12562306a36Sopenharmony_ci struct list_head list; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci struct device *hwmon_dev; 12862306a36Sopenharmony_ci struct platform_device *pdev; 12962306a36Sopenharmony_ci struct mutex lock; 13062306a36Sopenharmony_ci bool valid; 13162306a36Sopenharmony_ci unsigned long last_updated; /* In jiffies */ 13262306a36Sopenharmony_ci u8 ver_major; 13362306a36Sopenharmony_ci u8 ver_minor; 13462306a36Sopenharmony_ci u8 module_handle; 13562306a36Sopenharmony_ci int id; 13662306a36Sopenharmony_ci struct aem_ipmi_data ipmi; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* Function and buffer to update sensors */ 13962306a36Sopenharmony_ci void (*update)(struct aem_data *data); 14062306a36Sopenharmony_ci struct aem_read_sensor_resp *rs_resp; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* 14362306a36Sopenharmony_ci * AEM 1.x sensors: 14462306a36Sopenharmony_ci * Available sensors: 14562306a36Sopenharmony_ci * Energy meter 14662306a36Sopenharmony_ci * Power meter 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * AEM 2.x sensors: 14962306a36Sopenharmony_ci * Two energy meters 15062306a36Sopenharmony_ci * Two power meters 15162306a36Sopenharmony_ci * Two temperature sensors 15262306a36Sopenharmony_ci * Six power cap registers 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* sysfs attrs */ 15662306a36Sopenharmony_ci struct sensor_device_attribute sensors[AEM_NUM_SENSORS]; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* energy use in mJ */ 15962306a36Sopenharmony_ci u64 energy[AEM_NUM_ENERGY_REGS]; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* power sampling interval in ms */ 16262306a36Sopenharmony_ci unsigned long power_period[AEM_NUM_ENERGY_REGS]; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Everything past here is for AEM2 only */ 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* power caps in dW */ 16762306a36Sopenharmony_ci u16 pcap[AEM2_NUM_PCAP_REGS]; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* exhaust temperature in C */ 17062306a36Sopenharmony_ci u8 temp[AEM2_NUM_TEMP_REGS]; 17162306a36Sopenharmony_ci}; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci/* Data structures returned by the AEM firmware */ 17462306a36Sopenharmony_cistruct aem_iana_id { 17562306a36Sopenharmony_ci u8 bytes[3]; 17662306a36Sopenharmony_ci}; 17762306a36Sopenharmony_cistatic struct aem_iana_id system_x_id = { 17862306a36Sopenharmony_ci .bytes = {0x4D, 0x4F, 0x00} 17962306a36Sopenharmony_ci}; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci/* These are used to find AEM1 instances */ 18262306a36Sopenharmony_cistruct aem_find_firmware_req { 18362306a36Sopenharmony_ci struct aem_iana_id id; 18462306a36Sopenharmony_ci u8 rsvd; 18562306a36Sopenharmony_ci __be16 index; 18662306a36Sopenharmony_ci __be16 module_type_id; 18762306a36Sopenharmony_ci} __packed; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistruct aem_find_firmware_resp { 19062306a36Sopenharmony_ci struct aem_iana_id id; 19162306a36Sopenharmony_ci u8 num_instances; 19262306a36Sopenharmony_ci} __packed; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci/* These are used to find AEM2 instances */ 19562306a36Sopenharmony_cistruct aem_find_instance_req { 19662306a36Sopenharmony_ci struct aem_iana_id id; 19762306a36Sopenharmony_ci u8 instance_number; 19862306a36Sopenharmony_ci __be16 module_type_id; 19962306a36Sopenharmony_ci} __packed; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistruct aem_find_instance_resp { 20262306a36Sopenharmony_ci struct aem_iana_id id; 20362306a36Sopenharmony_ci u8 num_instances; 20462306a36Sopenharmony_ci u8 major; 20562306a36Sopenharmony_ci u8 minor; 20662306a36Sopenharmony_ci u8 module_handle; 20762306a36Sopenharmony_ci u16 record_id; 20862306a36Sopenharmony_ci} __packed; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci/* These are used to query sensors */ 21162306a36Sopenharmony_cistruct aem_read_sensor_req { 21262306a36Sopenharmony_ci struct aem_iana_id id; 21362306a36Sopenharmony_ci u8 module_handle; 21462306a36Sopenharmony_ci u8 element; 21562306a36Sopenharmony_ci u8 subcommand; 21662306a36Sopenharmony_ci u8 reg; 21762306a36Sopenharmony_ci u8 rx_buf_size; 21862306a36Sopenharmony_ci} __packed; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistruct aem_read_sensor_resp { 22162306a36Sopenharmony_ci struct aem_iana_id id; 22262306a36Sopenharmony_ci u8 bytes[]; 22362306a36Sopenharmony_ci} __packed; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci/* Data structures to talk to the IPMI layer */ 22662306a36Sopenharmony_cistruct aem_driver_data { 22762306a36Sopenharmony_ci struct list_head aem_devices; 22862306a36Sopenharmony_ci struct ipmi_smi_watcher bmc_events; 22962306a36Sopenharmony_ci struct ipmi_user_hndl ipmi_hndlrs; 23062306a36Sopenharmony_ci}; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic void aem_register_bmc(int iface, struct device *dev); 23362306a36Sopenharmony_cistatic void aem_bmc_gone(int iface); 23462306a36Sopenharmony_cistatic void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic void aem_remove_sensors(struct aem_data *data); 23762306a36Sopenharmony_cistatic int aem1_find_sensors(struct aem_data *data); 23862306a36Sopenharmony_cistatic int aem2_find_sensors(struct aem_data *data); 23962306a36Sopenharmony_cistatic void update_aem1_sensors(struct aem_data *data); 24062306a36Sopenharmony_cistatic void update_aem2_sensors(struct aem_data *data); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic struct aem_driver_data driver_data = { 24362306a36Sopenharmony_ci .aem_devices = LIST_HEAD_INIT(driver_data.aem_devices), 24462306a36Sopenharmony_ci .bmc_events = { 24562306a36Sopenharmony_ci .owner = THIS_MODULE, 24662306a36Sopenharmony_ci .new_smi = aem_register_bmc, 24762306a36Sopenharmony_ci .smi_gone = aem_bmc_gone, 24862306a36Sopenharmony_ci }, 24962306a36Sopenharmony_ci .ipmi_hndlrs = { 25062306a36Sopenharmony_ci .ipmi_recv_hndl = aem_msg_handler, 25162306a36Sopenharmony_ci }, 25262306a36Sopenharmony_ci}; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/* Functions to talk to the IPMI layer */ 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci/* Initialize IPMI address, message buffers and user data */ 25762306a36Sopenharmony_cistatic int aem_init_ipmi_data(struct aem_ipmi_data *data, int iface, 25862306a36Sopenharmony_ci struct device *bmc) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci int err; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci init_completion(&data->read_complete); 26362306a36Sopenharmony_ci data->bmc_device = bmc; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* Initialize IPMI address */ 26662306a36Sopenharmony_ci data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; 26762306a36Sopenharmony_ci data->address.channel = IPMI_BMC_CHANNEL; 26862306a36Sopenharmony_ci data->address.data[0] = 0; 26962306a36Sopenharmony_ci data->interface = iface; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* Initialize message buffers */ 27262306a36Sopenharmony_ci data->tx_msgid = 0; 27362306a36Sopenharmony_ci data->tx_message.netfn = AEM_NETFN; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* Create IPMI messaging interface user */ 27662306a36Sopenharmony_ci err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs, 27762306a36Sopenharmony_ci data, &data->user); 27862306a36Sopenharmony_ci if (err < 0) { 27962306a36Sopenharmony_ci dev_err(bmc, 28062306a36Sopenharmony_ci "Unable to register user with IPMI interface %d\n", 28162306a36Sopenharmony_ci data->interface); 28262306a36Sopenharmony_ci return err; 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci return 0; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci/* Send an IPMI command */ 28962306a36Sopenharmony_cistatic int aem_send_message(struct aem_ipmi_data *data) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci int err; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci err = ipmi_validate_addr(&data->address, sizeof(data->address)); 29462306a36Sopenharmony_ci if (err) 29562306a36Sopenharmony_ci goto out; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci data->tx_msgid++; 29862306a36Sopenharmony_ci err = ipmi_request_settime(data->user, &data->address, data->tx_msgid, 29962306a36Sopenharmony_ci &data->tx_message, data, 0, 0, 0); 30062306a36Sopenharmony_ci if (err) 30162306a36Sopenharmony_ci goto out1; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci return 0; 30462306a36Sopenharmony_ciout1: 30562306a36Sopenharmony_ci dev_err(data->bmc_device, "request_settime=%x\n", err); 30662306a36Sopenharmony_ci return err; 30762306a36Sopenharmony_ciout: 30862306a36Sopenharmony_ci dev_err(data->bmc_device, "validate_addr=%x\n", err); 30962306a36Sopenharmony_ci return err; 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci/* Dispatch IPMI messages to callers */ 31362306a36Sopenharmony_cistatic void aem_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci unsigned short rx_len; 31662306a36Sopenharmony_ci struct aem_ipmi_data *data = user_msg_data; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci if (msg->msgid != data->tx_msgid) { 31962306a36Sopenharmony_ci dev_err(data->bmc_device, 32062306a36Sopenharmony_ci "Mismatch between received msgid (%02x) and transmitted msgid (%02x)!\n", 32162306a36Sopenharmony_ci (int)msg->msgid, 32262306a36Sopenharmony_ci (int)data->tx_msgid); 32362306a36Sopenharmony_ci ipmi_free_recv_msg(msg); 32462306a36Sopenharmony_ci return; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci data->rx_recv_type = msg->recv_type; 32862306a36Sopenharmony_ci if (msg->msg.data_len > 0) 32962306a36Sopenharmony_ci data->rx_result = msg->msg.data[0]; 33062306a36Sopenharmony_ci else 33162306a36Sopenharmony_ci data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (msg->msg.data_len > 1) { 33462306a36Sopenharmony_ci rx_len = msg->msg.data_len - 1; 33562306a36Sopenharmony_ci if (data->rx_msg_len < rx_len) 33662306a36Sopenharmony_ci rx_len = data->rx_msg_len; 33762306a36Sopenharmony_ci data->rx_msg_len = rx_len; 33862306a36Sopenharmony_ci memcpy(data->rx_msg_data, msg->msg.data + 1, data->rx_msg_len); 33962306a36Sopenharmony_ci } else 34062306a36Sopenharmony_ci data->rx_msg_len = 0; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci ipmi_free_recv_msg(msg); 34362306a36Sopenharmony_ci complete(&data->read_complete); 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci/* Sensor support functions */ 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci/* Read a sensor value; must be called with data->lock held */ 34962306a36Sopenharmony_cistatic int aem_read_sensor(struct aem_data *data, u8 elt, u8 reg, 35062306a36Sopenharmony_ci void *buf, size_t size) 35162306a36Sopenharmony_ci{ 35262306a36Sopenharmony_ci int rs_size, res; 35362306a36Sopenharmony_ci struct aem_read_sensor_req rs_req; 35462306a36Sopenharmony_ci /* Use preallocated rx buffer */ 35562306a36Sopenharmony_ci struct aem_read_sensor_resp *rs_resp = data->rs_resp; 35662306a36Sopenharmony_ci struct aem_ipmi_data *ipmi = &data->ipmi; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci /* AEM registers are 1, 2, 4 or 8 bytes */ 35962306a36Sopenharmony_ci switch (size) { 36062306a36Sopenharmony_ci case 1: 36162306a36Sopenharmony_ci case 2: 36262306a36Sopenharmony_ci case 4: 36362306a36Sopenharmony_ci case 8: 36462306a36Sopenharmony_ci break; 36562306a36Sopenharmony_ci default: 36662306a36Sopenharmony_ci return -EINVAL; 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci rs_req.id = system_x_id; 37062306a36Sopenharmony_ci rs_req.module_handle = data->module_handle; 37162306a36Sopenharmony_ci rs_req.element = elt; 37262306a36Sopenharmony_ci rs_req.subcommand = AEM_READ_REGISTER; 37362306a36Sopenharmony_ci rs_req.reg = reg; 37462306a36Sopenharmony_ci rs_req.rx_buf_size = size; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci ipmi->tx_message.cmd = AEM_ELEMENT_CMD; 37762306a36Sopenharmony_ci ipmi->tx_message.data = (char *)&rs_req; 37862306a36Sopenharmony_ci ipmi->tx_message.data_len = sizeof(rs_req); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci rs_size = sizeof(*rs_resp) + size; 38162306a36Sopenharmony_ci ipmi->rx_msg_data = rs_resp; 38262306a36Sopenharmony_ci ipmi->rx_msg_len = rs_size; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci aem_send_message(ipmi); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci res = wait_for_completion_timeout(&ipmi->read_complete, IPMI_TIMEOUT); 38762306a36Sopenharmony_ci if (!res) { 38862306a36Sopenharmony_ci res = -ETIMEDOUT; 38962306a36Sopenharmony_ci goto out; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci if (ipmi->rx_result || ipmi->rx_msg_len != rs_size || 39362306a36Sopenharmony_ci memcmp(&rs_resp->id, &system_x_id, sizeof(system_x_id))) { 39462306a36Sopenharmony_ci res = -ENOENT; 39562306a36Sopenharmony_ci goto out; 39662306a36Sopenharmony_ci } 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci switch (size) { 39962306a36Sopenharmony_ci case 1: { 40062306a36Sopenharmony_ci u8 *x = buf; 40162306a36Sopenharmony_ci *x = rs_resp->bytes[0]; 40262306a36Sopenharmony_ci break; 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci case 2: { 40562306a36Sopenharmony_ci u16 *x = buf; 40662306a36Sopenharmony_ci *x = be16_to_cpup((__be16 *)rs_resp->bytes); 40762306a36Sopenharmony_ci break; 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci case 4: { 41062306a36Sopenharmony_ci u32 *x = buf; 41162306a36Sopenharmony_ci *x = be32_to_cpup((__be32 *)rs_resp->bytes); 41262306a36Sopenharmony_ci break; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci case 8: { 41562306a36Sopenharmony_ci u64 *x = buf; 41662306a36Sopenharmony_ci *x = be64_to_cpup((__be64 *)rs_resp->bytes); 41762306a36Sopenharmony_ci break; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci res = 0; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ciout: 42362306a36Sopenharmony_ci return res; 42462306a36Sopenharmony_ci} 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci/* Update AEM energy registers */ 42762306a36Sopenharmony_cistatic void update_aem_energy_one(struct aem_data *data, int which) 42862306a36Sopenharmony_ci{ 42962306a36Sopenharmony_ci aem_read_sensor(data, AEM_ENERGY_ELEMENT, which, 43062306a36Sopenharmony_ci &data->energy[which], 8); 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic void update_aem_energy(struct aem_data *data) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci update_aem_energy_one(data, 0); 43662306a36Sopenharmony_ci if (data->ver_major < 2) 43762306a36Sopenharmony_ci return; 43862306a36Sopenharmony_ci update_aem_energy_one(data, 1); 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci/* Update all AEM1 sensors */ 44262306a36Sopenharmony_cistatic void update_aem1_sensors(struct aem_data *data) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci mutex_lock(&data->lock); 44562306a36Sopenharmony_ci if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) && 44662306a36Sopenharmony_ci data->valid) 44762306a36Sopenharmony_ci goto out; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci update_aem_energy(data); 45062306a36Sopenharmony_ciout: 45162306a36Sopenharmony_ci mutex_unlock(&data->lock); 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci/* Update all AEM2 sensors */ 45562306a36Sopenharmony_cistatic void update_aem2_sensors(struct aem_data *data) 45662306a36Sopenharmony_ci{ 45762306a36Sopenharmony_ci int i; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci mutex_lock(&data->lock); 46062306a36Sopenharmony_ci if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) && 46162306a36Sopenharmony_ci data->valid) 46262306a36Sopenharmony_ci goto out; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci update_aem_energy(data); 46562306a36Sopenharmony_ci aem_read_sensor(data, AEM_EXHAUST_ELEMENT, 0, &data->temp[0], 1); 46662306a36Sopenharmony_ci aem_read_sensor(data, AEM_EXHAUST_ELEMENT, 1, &data->temp[1], 1); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci for (i = POWER_CAP; i <= POWER_AUX; i++) 46962306a36Sopenharmony_ci aem_read_sensor(data, AEM_POWER_CAP_ELEMENT, i, 47062306a36Sopenharmony_ci &data->pcap[i], 2); 47162306a36Sopenharmony_ciout: 47262306a36Sopenharmony_ci mutex_unlock(&data->lock); 47362306a36Sopenharmony_ci} 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci/* Delete an AEM instance */ 47662306a36Sopenharmony_cistatic void aem_delete(struct aem_data *data) 47762306a36Sopenharmony_ci{ 47862306a36Sopenharmony_ci list_del(&data->list); 47962306a36Sopenharmony_ci aem_remove_sensors(data); 48062306a36Sopenharmony_ci kfree(data->rs_resp); 48162306a36Sopenharmony_ci hwmon_device_unregister(data->hwmon_dev); 48262306a36Sopenharmony_ci ipmi_destroy_user(data->ipmi.user); 48362306a36Sopenharmony_ci platform_set_drvdata(data->pdev, NULL); 48462306a36Sopenharmony_ci platform_device_unregister(data->pdev); 48562306a36Sopenharmony_ci ida_free(&aem_ida, data->id); 48662306a36Sopenharmony_ci kfree(data); 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci/* Probe functions for AEM1 devices */ 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci/* Retrieve version and module handle for an AEM1 instance */ 49262306a36Sopenharmony_cistatic int aem_find_aem1_count(struct aem_ipmi_data *data) 49362306a36Sopenharmony_ci{ 49462306a36Sopenharmony_ci int res; 49562306a36Sopenharmony_ci struct aem_find_firmware_req ff_req; 49662306a36Sopenharmony_ci struct aem_find_firmware_resp ff_resp; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci ff_req.id = system_x_id; 49962306a36Sopenharmony_ci ff_req.index = 0; 50062306a36Sopenharmony_ci ff_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci data->tx_message.cmd = AEM_FIND_FW_CMD; 50362306a36Sopenharmony_ci data->tx_message.data = (char *)&ff_req; 50462306a36Sopenharmony_ci data->tx_message.data_len = sizeof(ff_req); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci data->rx_msg_data = &ff_resp; 50762306a36Sopenharmony_ci data->rx_msg_len = sizeof(ff_resp); 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci aem_send_message(data); 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci res = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT); 51262306a36Sopenharmony_ci if (!res) 51362306a36Sopenharmony_ci return -ETIMEDOUT; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci if (data->rx_result || data->rx_msg_len != sizeof(ff_resp) || 51662306a36Sopenharmony_ci memcmp(&ff_resp.id, &system_x_id, sizeof(system_x_id))) 51762306a36Sopenharmony_ci return -ENOENT; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci return ff_resp.num_instances; 52062306a36Sopenharmony_ci} 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci/* Find and initialize one AEM1 instance */ 52362306a36Sopenharmony_cistatic int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle) 52462306a36Sopenharmony_ci{ 52562306a36Sopenharmony_ci struct aem_data *data; 52662306a36Sopenharmony_ci int i; 52762306a36Sopenharmony_ci int res = -ENOMEM; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci data = kzalloc(sizeof(*data), GFP_KERNEL); 53062306a36Sopenharmony_ci if (!data) 53162306a36Sopenharmony_ci return res; 53262306a36Sopenharmony_ci mutex_init(&data->lock); 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci /* Copy instance data */ 53562306a36Sopenharmony_ci data->ver_major = 1; 53662306a36Sopenharmony_ci data->ver_minor = 0; 53762306a36Sopenharmony_ci data->module_handle = module_handle; 53862306a36Sopenharmony_ci for (i = 0; i < AEM1_NUM_ENERGY_REGS; i++) 53962306a36Sopenharmony_ci data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci /* Create sub-device for this fw instance */ 54262306a36Sopenharmony_ci data->id = ida_alloc(&aem_ida, GFP_KERNEL); 54362306a36Sopenharmony_ci if (data->id < 0) 54462306a36Sopenharmony_ci goto id_err; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci data->pdev = platform_device_alloc(DRVNAME, data->id); 54762306a36Sopenharmony_ci if (!data->pdev) 54862306a36Sopenharmony_ci goto dev_err; 54962306a36Sopenharmony_ci data->pdev->dev.driver = &aem_driver.driver; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci res = platform_device_add(data->pdev); 55262306a36Sopenharmony_ci if (res) 55362306a36Sopenharmony_ci goto dev_add_err; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci platform_set_drvdata(data->pdev, data); 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci /* Set up IPMI interface */ 55862306a36Sopenharmony_ci res = aem_init_ipmi_data(&data->ipmi, probe->interface, 55962306a36Sopenharmony_ci probe->bmc_device); 56062306a36Sopenharmony_ci if (res) 56162306a36Sopenharmony_ci goto ipmi_err; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci /* Register with hwmon */ 56462306a36Sopenharmony_ci data->hwmon_dev = hwmon_device_register(&data->pdev->dev); 56562306a36Sopenharmony_ci if (IS_ERR(data->hwmon_dev)) { 56662306a36Sopenharmony_ci dev_err(&data->pdev->dev, 56762306a36Sopenharmony_ci "Unable to register hwmon device for IPMI interface %d\n", 56862306a36Sopenharmony_ci probe->interface); 56962306a36Sopenharmony_ci res = PTR_ERR(data->hwmon_dev); 57062306a36Sopenharmony_ci goto hwmon_reg_err; 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci data->update = update_aem1_sensors; 57462306a36Sopenharmony_ci data->rs_resp = kzalloc(sizeof(*(data->rs_resp)) + 8, GFP_KERNEL); 57562306a36Sopenharmony_ci if (!data->rs_resp) { 57662306a36Sopenharmony_ci res = -ENOMEM; 57762306a36Sopenharmony_ci goto alloc_resp_err; 57862306a36Sopenharmony_ci } 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci /* Find sensors */ 58162306a36Sopenharmony_ci res = aem1_find_sensors(data); 58262306a36Sopenharmony_ci if (res) 58362306a36Sopenharmony_ci goto sensor_err; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci /* Add to our list of AEM devices */ 58662306a36Sopenharmony_ci list_add_tail(&data->list, &driver_data.aem_devices); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci dev_info(data->ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n", 58962306a36Sopenharmony_ci data->ver_major, data->ver_minor, 59062306a36Sopenharmony_ci data->module_handle); 59162306a36Sopenharmony_ci return 0; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_cisensor_err: 59462306a36Sopenharmony_ci kfree(data->rs_resp); 59562306a36Sopenharmony_cialloc_resp_err: 59662306a36Sopenharmony_ci hwmon_device_unregister(data->hwmon_dev); 59762306a36Sopenharmony_cihwmon_reg_err: 59862306a36Sopenharmony_ci ipmi_destroy_user(data->ipmi.user); 59962306a36Sopenharmony_ciipmi_err: 60062306a36Sopenharmony_ci platform_set_drvdata(data->pdev, NULL); 60162306a36Sopenharmony_ci platform_device_del(data->pdev); 60262306a36Sopenharmony_cidev_add_err: 60362306a36Sopenharmony_ci platform_device_put(data->pdev); 60462306a36Sopenharmony_cidev_err: 60562306a36Sopenharmony_ci ida_free(&aem_ida, data->id); 60662306a36Sopenharmony_ciid_err: 60762306a36Sopenharmony_ci kfree(data); 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci return res; 61062306a36Sopenharmony_ci} 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci/* Find and initialize all AEM1 instances */ 61362306a36Sopenharmony_cistatic void aem_init_aem1(struct aem_ipmi_data *probe) 61462306a36Sopenharmony_ci{ 61562306a36Sopenharmony_ci int num, i, err; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci num = aem_find_aem1_count(probe); 61862306a36Sopenharmony_ci for (i = 0; i < num; i++) { 61962306a36Sopenharmony_ci err = aem_init_aem1_inst(probe, i); 62062306a36Sopenharmony_ci if (err) { 62162306a36Sopenharmony_ci dev_err(probe->bmc_device, 62262306a36Sopenharmony_ci "Error %d initializing AEM1 0x%X\n", 62362306a36Sopenharmony_ci err, i); 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci/* Probe functions for AEM2 devices */ 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci/* Retrieve version and module handle for an AEM2 instance */ 63162306a36Sopenharmony_cistatic int aem_find_aem2(struct aem_ipmi_data *data, 63262306a36Sopenharmony_ci struct aem_find_instance_resp *fi_resp, 63362306a36Sopenharmony_ci int instance_num) 63462306a36Sopenharmony_ci{ 63562306a36Sopenharmony_ci int res; 63662306a36Sopenharmony_ci struct aem_find_instance_req fi_req; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci fi_req.id = system_x_id; 63962306a36Sopenharmony_ci fi_req.instance_number = instance_num; 64062306a36Sopenharmony_ci fi_req.module_type_id = cpu_to_be16(AEM_MODULE_TYPE_ID); 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci data->tx_message.cmd = AEM_FW_INSTANCE_CMD; 64362306a36Sopenharmony_ci data->tx_message.data = (char *)&fi_req; 64462306a36Sopenharmony_ci data->tx_message.data_len = sizeof(fi_req); 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci data->rx_msg_data = fi_resp; 64762306a36Sopenharmony_ci data->rx_msg_len = sizeof(*fi_resp); 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci aem_send_message(data); 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci res = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT); 65262306a36Sopenharmony_ci if (!res) 65362306a36Sopenharmony_ci return -ETIMEDOUT; 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci if (data->rx_result || data->rx_msg_len != sizeof(*fi_resp) || 65662306a36Sopenharmony_ci memcmp(&fi_resp->id, &system_x_id, sizeof(system_x_id)) || 65762306a36Sopenharmony_ci fi_resp->num_instances <= instance_num) 65862306a36Sopenharmony_ci return -ENOENT; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci return 0; 66162306a36Sopenharmony_ci} 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci/* Find and initialize one AEM2 instance */ 66462306a36Sopenharmony_cistatic int aem_init_aem2_inst(struct aem_ipmi_data *probe, 66562306a36Sopenharmony_ci struct aem_find_instance_resp *fi_resp) 66662306a36Sopenharmony_ci{ 66762306a36Sopenharmony_ci struct aem_data *data; 66862306a36Sopenharmony_ci int i; 66962306a36Sopenharmony_ci int res = -ENOMEM; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci data = kzalloc(sizeof(*data), GFP_KERNEL); 67262306a36Sopenharmony_ci if (!data) 67362306a36Sopenharmony_ci return res; 67462306a36Sopenharmony_ci mutex_init(&data->lock); 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci /* Copy instance data */ 67762306a36Sopenharmony_ci data->ver_major = fi_resp->major; 67862306a36Sopenharmony_ci data->ver_minor = fi_resp->minor; 67962306a36Sopenharmony_ci data->module_handle = fi_resp->module_handle; 68062306a36Sopenharmony_ci for (i = 0; i < AEM2_NUM_ENERGY_REGS; i++) 68162306a36Sopenharmony_ci data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci /* Create sub-device for this fw instance */ 68462306a36Sopenharmony_ci data->id = ida_alloc(&aem_ida, GFP_KERNEL); 68562306a36Sopenharmony_ci if (data->id < 0) 68662306a36Sopenharmony_ci goto id_err; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci data->pdev = platform_device_alloc(DRVNAME, data->id); 68962306a36Sopenharmony_ci if (!data->pdev) 69062306a36Sopenharmony_ci goto dev_err; 69162306a36Sopenharmony_ci data->pdev->dev.driver = &aem_driver.driver; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci res = platform_device_add(data->pdev); 69462306a36Sopenharmony_ci if (res) 69562306a36Sopenharmony_ci goto dev_add_err; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci platform_set_drvdata(data->pdev, data); 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci /* Set up IPMI interface */ 70062306a36Sopenharmony_ci res = aem_init_ipmi_data(&data->ipmi, probe->interface, 70162306a36Sopenharmony_ci probe->bmc_device); 70262306a36Sopenharmony_ci if (res) 70362306a36Sopenharmony_ci goto ipmi_err; 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci /* Register with hwmon */ 70662306a36Sopenharmony_ci data->hwmon_dev = hwmon_device_register(&data->pdev->dev); 70762306a36Sopenharmony_ci if (IS_ERR(data->hwmon_dev)) { 70862306a36Sopenharmony_ci dev_err(&data->pdev->dev, 70962306a36Sopenharmony_ci "Unable to register hwmon device for IPMI interface %d\n", 71062306a36Sopenharmony_ci probe->interface); 71162306a36Sopenharmony_ci res = PTR_ERR(data->hwmon_dev); 71262306a36Sopenharmony_ci goto hwmon_reg_err; 71362306a36Sopenharmony_ci } 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci data->update = update_aem2_sensors; 71662306a36Sopenharmony_ci data->rs_resp = kzalloc(sizeof(*(data->rs_resp)) + 8, GFP_KERNEL); 71762306a36Sopenharmony_ci if (!data->rs_resp) { 71862306a36Sopenharmony_ci res = -ENOMEM; 71962306a36Sopenharmony_ci goto alloc_resp_err; 72062306a36Sopenharmony_ci } 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci /* Find sensors */ 72362306a36Sopenharmony_ci res = aem2_find_sensors(data); 72462306a36Sopenharmony_ci if (res) 72562306a36Sopenharmony_ci goto sensor_err; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci /* Add to our list of AEM devices */ 72862306a36Sopenharmony_ci list_add_tail(&data->list, &driver_data.aem_devices); 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci dev_info(data->ipmi.bmc_device, "Found AEM v%d.%d at 0x%X\n", 73162306a36Sopenharmony_ci data->ver_major, data->ver_minor, 73262306a36Sopenharmony_ci data->module_handle); 73362306a36Sopenharmony_ci return 0; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_cisensor_err: 73662306a36Sopenharmony_ci kfree(data->rs_resp); 73762306a36Sopenharmony_cialloc_resp_err: 73862306a36Sopenharmony_ci hwmon_device_unregister(data->hwmon_dev); 73962306a36Sopenharmony_cihwmon_reg_err: 74062306a36Sopenharmony_ci ipmi_destroy_user(data->ipmi.user); 74162306a36Sopenharmony_ciipmi_err: 74262306a36Sopenharmony_ci platform_set_drvdata(data->pdev, NULL); 74362306a36Sopenharmony_ci platform_device_del(data->pdev); 74462306a36Sopenharmony_cidev_add_err: 74562306a36Sopenharmony_ci platform_device_put(data->pdev); 74662306a36Sopenharmony_cidev_err: 74762306a36Sopenharmony_ci ida_free(&aem_ida, data->id); 74862306a36Sopenharmony_ciid_err: 74962306a36Sopenharmony_ci kfree(data); 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci return res; 75262306a36Sopenharmony_ci} 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci/* Find and initialize all AEM2 instances */ 75562306a36Sopenharmony_cistatic void aem_init_aem2(struct aem_ipmi_data *probe) 75662306a36Sopenharmony_ci{ 75762306a36Sopenharmony_ci struct aem_find_instance_resp fi_resp; 75862306a36Sopenharmony_ci int err; 75962306a36Sopenharmony_ci int i = 0; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci while (!aem_find_aem2(probe, &fi_resp, i)) { 76262306a36Sopenharmony_ci if (fi_resp.major != 2) { 76362306a36Sopenharmony_ci dev_err(probe->bmc_device, 76462306a36Sopenharmony_ci "Unknown AEM v%d; please report this to the maintainer.\n", 76562306a36Sopenharmony_ci fi_resp.major); 76662306a36Sopenharmony_ci i++; 76762306a36Sopenharmony_ci continue; 76862306a36Sopenharmony_ci } 76962306a36Sopenharmony_ci err = aem_init_aem2_inst(probe, &fi_resp); 77062306a36Sopenharmony_ci if (err) { 77162306a36Sopenharmony_ci dev_err(probe->bmc_device, 77262306a36Sopenharmony_ci "Error %d initializing AEM2 0x%X\n", 77362306a36Sopenharmony_ci err, fi_resp.module_handle); 77462306a36Sopenharmony_ci } 77562306a36Sopenharmony_ci i++; 77662306a36Sopenharmony_ci } 77762306a36Sopenharmony_ci} 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci/* Probe a BMC for AEM firmware instances */ 78062306a36Sopenharmony_cistatic void aem_register_bmc(int iface, struct device *dev) 78162306a36Sopenharmony_ci{ 78262306a36Sopenharmony_ci struct aem_ipmi_data probe; 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci if (aem_init_ipmi_data(&probe, iface, dev)) 78562306a36Sopenharmony_ci return; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci /* Ignore probe errors; they won't cause problems */ 78862306a36Sopenharmony_ci aem_init_aem1(&probe); 78962306a36Sopenharmony_ci aem_init_aem2(&probe); 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci ipmi_destroy_user(probe.user); 79262306a36Sopenharmony_ci} 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci/* Handle BMC deletion */ 79562306a36Sopenharmony_cistatic void aem_bmc_gone(int iface) 79662306a36Sopenharmony_ci{ 79762306a36Sopenharmony_ci struct aem_data *p1, *next1; 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci list_for_each_entry_safe(p1, next1, &driver_data.aem_devices, list) 80062306a36Sopenharmony_ci if (p1->ipmi.interface == iface) 80162306a36Sopenharmony_ci aem_delete(p1); 80262306a36Sopenharmony_ci} 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci/* sysfs support functions */ 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci/* AEM device name */ 80762306a36Sopenharmony_cistatic ssize_t name_show(struct device *dev, struct device_attribute *devattr, 80862306a36Sopenharmony_ci char *buf) 80962306a36Sopenharmony_ci{ 81062306a36Sopenharmony_ci struct aem_data *data = dev_get_drvdata(dev); 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci return sprintf(buf, "%s%d\n", DRVNAME, data->ver_major); 81362306a36Sopenharmony_ci} 81462306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(name, name, 0); 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci/* AEM device version */ 81762306a36Sopenharmony_cistatic ssize_t version_show(struct device *dev, 81862306a36Sopenharmony_ci struct device_attribute *devattr, char *buf) 81962306a36Sopenharmony_ci{ 82062306a36Sopenharmony_ci struct aem_data *data = dev_get_drvdata(dev); 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci return sprintf(buf, "%d.%d\n", data->ver_major, data->ver_minor); 82362306a36Sopenharmony_ci} 82462306a36Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(version, version, 0); 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci/* Display power use */ 82762306a36Sopenharmony_cistatic ssize_t aem_show_power(struct device *dev, 82862306a36Sopenharmony_ci struct device_attribute *devattr, 82962306a36Sopenharmony_ci char *buf) 83062306a36Sopenharmony_ci{ 83162306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 83262306a36Sopenharmony_ci struct aem_data *data = dev_get_drvdata(dev); 83362306a36Sopenharmony_ci u64 before, after, delta, time; 83462306a36Sopenharmony_ci signed long leftover; 83562306a36Sopenharmony_ci 83662306a36Sopenharmony_ci mutex_lock(&data->lock); 83762306a36Sopenharmony_ci update_aem_energy_one(data, attr->index); 83862306a36Sopenharmony_ci time = ktime_get_ns(); 83962306a36Sopenharmony_ci before = data->energy[attr->index]; 84062306a36Sopenharmony_ci 84162306a36Sopenharmony_ci leftover = schedule_timeout_interruptible( 84262306a36Sopenharmony_ci msecs_to_jiffies(data->power_period[attr->index]) 84362306a36Sopenharmony_ci ); 84462306a36Sopenharmony_ci if (leftover) { 84562306a36Sopenharmony_ci mutex_unlock(&data->lock); 84662306a36Sopenharmony_ci return 0; 84762306a36Sopenharmony_ci } 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci update_aem_energy_one(data, attr->index); 85062306a36Sopenharmony_ci time = ktime_get_ns() - time; 85162306a36Sopenharmony_ci after = data->energy[attr->index]; 85262306a36Sopenharmony_ci mutex_unlock(&data->lock); 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci delta = (after - before) * UJ_PER_MJ; 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci return sprintf(buf, "%llu\n", 85762306a36Sopenharmony_ci (unsigned long long)div64_u64(delta * NSEC_PER_SEC, time)); 85862306a36Sopenharmony_ci} 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci/* Display energy use */ 86162306a36Sopenharmony_cistatic ssize_t aem_show_energy(struct device *dev, 86262306a36Sopenharmony_ci struct device_attribute *devattr, 86362306a36Sopenharmony_ci char *buf) 86462306a36Sopenharmony_ci{ 86562306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 86662306a36Sopenharmony_ci struct aem_data *a = dev_get_drvdata(dev); 86762306a36Sopenharmony_ci mutex_lock(&a->lock); 86862306a36Sopenharmony_ci update_aem_energy_one(a, attr->index); 86962306a36Sopenharmony_ci mutex_unlock(&a->lock); 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci return sprintf(buf, "%llu\n", 87262306a36Sopenharmony_ci (unsigned long long)a->energy[attr->index] * 1000); 87362306a36Sopenharmony_ci} 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci/* Display power interval registers */ 87662306a36Sopenharmony_cistatic ssize_t aem_show_power_period(struct device *dev, 87762306a36Sopenharmony_ci struct device_attribute *devattr, 87862306a36Sopenharmony_ci char *buf) 87962306a36Sopenharmony_ci{ 88062306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 88162306a36Sopenharmony_ci struct aem_data *a = dev_get_drvdata(dev); 88262306a36Sopenharmony_ci a->update(a); 88362306a36Sopenharmony_ci 88462306a36Sopenharmony_ci return sprintf(buf, "%lu\n", a->power_period[attr->index]); 88562306a36Sopenharmony_ci} 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci/* Set power interval registers */ 88862306a36Sopenharmony_cistatic ssize_t aem_set_power_period(struct device *dev, 88962306a36Sopenharmony_ci struct device_attribute *devattr, 89062306a36Sopenharmony_ci const char *buf, size_t count) 89162306a36Sopenharmony_ci{ 89262306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 89362306a36Sopenharmony_ci struct aem_data *a = dev_get_drvdata(dev); 89462306a36Sopenharmony_ci unsigned long temp; 89562306a36Sopenharmony_ci int res; 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci res = kstrtoul(buf, 10, &temp); 89862306a36Sopenharmony_ci if (res) 89962306a36Sopenharmony_ci return res; 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci if (temp < AEM_MIN_POWER_INTERVAL) 90262306a36Sopenharmony_ci return -EINVAL; 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci mutex_lock(&a->lock); 90562306a36Sopenharmony_ci a->power_period[attr->index] = temp; 90662306a36Sopenharmony_ci mutex_unlock(&a->lock); 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci return count; 90962306a36Sopenharmony_ci} 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ci/* Discover sensors on an AEM device */ 91262306a36Sopenharmony_cistatic int aem_register_sensors(struct aem_data *data, 91362306a36Sopenharmony_ci const struct aem_ro_sensor_template *ro, 91462306a36Sopenharmony_ci const struct aem_rw_sensor_template *rw) 91562306a36Sopenharmony_ci{ 91662306a36Sopenharmony_ci struct device *dev = &data->pdev->dev; 91762306a36Sopenharmony_ci struct sensor_device_attribute *sensors = data->sensors; 91862306a36Sopenharmony_ci int err; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci /* Set up read-only sensors */ 92162306a36Sopenharmony_ci while (ro->label) { 92262306a36Sopenharmony_ci sysfs_attr_init(&sensors->dev_attr.attr); 92362306a36Sopenharmony_ci sensors->dev_attr.attr.name = ro->label; 92462306a36Sopenharmony_ci sensors->dev_attr.attr.mode = 0444; 92562306a36Sopenharmony_ci sensors->dev_attr.show = ro->show; 92662306a36Sopenharmony_ci sensors->index = ro->index; 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci err = device_create_file(dev, &sensors->dev_attr); 92962306a36Sopenharmony_ci if (err) { 93062306a36Sopenharmony_ci sensors->dev_attr.attr.name = NULL; 93162306a36Sopenharmony_ci goto error; 93262306a36Sopenharmony_ci } 93362306a36Sopenharmony_ci sensors++; 93462306a36Sopenharmony_ci ro++; 93562306a36Sopenharmony_ci } 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci /* Set up read-write sensors */ 93862306a36Sopenharmony_ci while (rw->label) { 93962306a36Sopenharmony_ci sysfs_attr_init(&sensors->dev_attr.attr); 94062306a36Sopenharmony_ci sensors->dev_attr.attr.name = rw->label; 94162306a36Sopenharmony_ci sensors->dev_attr.attr.mode = 0644; 94262306a36Sopenharmony_ci sensors->dev_attr.show = rw->show; 94362306a36Sopenharmony_ci sensors->dev_attr.store = rw->set; 94462306a36Sopenharmony_ci sensors->index = rw->index; 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci err = device_create_file(dev, &sensors->dev_attr); 94762306a36Sopenharmony_ci if (err) { 94862306a36Sopenharmony_ci sensors->dev_attr.attr.name = NULL; 94962306a36Sopenharmony_ci goto error; 95062306a36Sopenharmony_ci } 95162306a36Sopenharmony_ci sensors++; 95262306a36Sopenharmony_ci rw++; 95362306a36Sopenharmony_ci } 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci err = device_create_file(dev, &sensor_dev_attr_name.dev_attr); 95662306a36Sopenharmony_ci if (err) 95762306a36Sopenharmony_ci goto error; 95862306a36Sopenharmony_ci err = device_create_file(dev, &sensor_dev_attr_version.dev_attr); 95962306a36Sopenharmony_ci return err; 96062306a36Sopenharmony_ci 96162306a36Sopenharmony_cierror: 96262306a36Sopenharmony_ci aem_remove_sensors(data); 96362306a36Sopenharmony_ci return err; 96462306a36Sopenharmony_ci} 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci/* sysfs support functions for AEM2 sensors */ 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci/* Display temperature use */ 96962306a36Sopenharmony_cistatic ssize_t aem2_show_temp(struct device *dev, 97062306a36Sopenharmony_ci struct device_attribute *devattr, 97162306a36Sopenharmony_ci char *buf) 97262306a36Sopenharmony_ci{ 97362306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 97462306a36Sopenharmony_ci struct aem_data *a = dev_get_drvdata(dev); 97562306a36Sopenharmony_ci a->update(a); 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_ci return sprintf(buf, "%u\n", a->temp[attr->index] * 1000); 97862306a36Sopenharmony_ci} 97962306a36Sopenharmony_ci 98062306a36Sopenharmony_ci/* Display power-capping registers */ 98162306a36Sopenharmony_cistatic ssize_t aem2_show_pcap_value(struct device *dev, 98262306a36Sopenharmony_ci struct device_attribute *devattr, 98362306a36Sopenharmony_ci char *buf) 98462306a36Sopenharmony_ci{ 98562306a36Sopenharmony_ci struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); 98662306a36Sopenharmony_ci struct aem_data *a = dev_get_drvdata(dev); 98762306a36Sopenharmony_ci a->update(a); 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_ci return sprintf(buf, "%u\n", a->pcap[attr->index] * 100000); 99062306a36Sopenharmony_ci} 99162306a36Sopenharmony_ci 99262306a36Sopenharmony_ci/* Remove sensors attached to an AEM device */ 99362306a36Sopenharmony_cistatic void aem_remove_sensors(struct aem_data *data) 99462306a36Sopenharmony_ci{ 99562306a36Sopenharmony_ci int i; 99662306a36Sopenharmony_ci 99762306a36Sopenharmony_ci for (i = 0; i < AEM_NUM_SENSORS; i++) { 99862306a36Sopenharmony_ci if (!data->sensors[i].dev_attr.attr.name) 99962306a36Sopenharmony_ci continue; 100062306a36Sopenharmony_ci device_remove_file(&data->pdev->dev, 100162306a36Sopenharmony_ci &data->sensors[i].dev_attr); 100262306a36Sopenharmony_ci } 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci device_remove_file(&data->pdev->dev, 100562306a36Sopenharmony_ci &sensor_dev_attr_name.dev_attr); 100662306a36Sopenharmony_ci device_remove_file(&data->pdev->dev, 100762306a36Sopenharmony_ci &sensor_dev_attr_version.dev_attr); 100862306a36Sopenharmony_ci} 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_ci/* Sensor probe functions */ 101162306a36Sopenharmony_ci 101262306a36Sopenharmony_ci/* Description of AEM1 sensors */ 101362306a36Sopenharmony_cistatic const struct aem_ro_sensor_template aem1_ro_sensors[] = { 101462306a36Sopenharmony_ci{"energy1_input", aem_show_energy, 0}, 101562306a36Sopenharmony_ci{"power1_average", aem_show_power, 0}, 101662306a36Sopenharmony_ci{NULL, NULL, 0}, 101762306a36Sopenharmony_ci}; 101862306a36Sopenharmony_ci 101962306a36Sopenharmony_cistatic const struct aem_rw_sensor_template aem1_rw_sensors[] = { 102062306a36Sopenharmony_ci{"power1_average_interval", aem_show_power_period, aem_set_power_period, 0}, 102162306a36Sopenharmony_ci{NULL, NULL, NULL, 0}, 102262306a36Sopenharmony_ci}; 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_ci/* Description of AEM2 sensors */ 102562306a36Sopenharmony_cistatic const struct aem_ro_sensor_template aem2_ro_sensors[] = { 102662306a36Sopenharmony_ci{"energy1_input", aem_show_energy, 0}, 102762306a36Sopenharmony_ci{"energy2_input", aem_show_energy, 1}, 102862306a36Sopenharmony_ci{"power1_average", aem_show_power, 0}, 102962306a36Sopenharmony_ci{"power2_average", aem_show_power, 1}, 103062306a36Sopenharmony_ci{"temp1_input", aem2_show_temp, 0}, 103162306a36Sopenharmony_ci{"temp2_input", aem2_show_temp, 1}, 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci{"power4_average", aem2_show_pcap_value, POWER_CAP_MAX_HOTPLUG}, 103462306a36Sopenharmony_ci{"power5_average", aem2_show_pcap_value, POWER_CAP_MAX}, 103562306a36Sopenharmony_ci{"power6_average", aem2_show_pcap_value, POWER_CAP_MIN_WARNING}, 103662306a36Sopenharmony_ci{"power7_average", aem2_show_pcap_value, POWER_CAP_MIN}, 103762306a36Sopenharmony_ci 103862306a36Sopenharmony_ci{"power3_average", aem2_show_pcap_value, POWER_AUX}, 103962306a36Sopenharmony_ci{"power_cap", aem2_show_pcap_value, POWER_CAP}, 104062306a36Sopenharmony_ci{NULL, NULL, 0}, 104162306a36Sopenharmony_ci}; 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_cistatic const struct aem_rw_sensor_template aem2_rw_sensors[] = { 104462306a36Sopenharmony_ci{"power1_average_interval", aem_show_power_period, aem_set_power_period, 0}, 104562306a36Sopenharmony_ci{"power2_average_interval", aem_show_power_period, aem_set_power_period, 1}, 104662306a36Sopenharmony_ci{NULL, NULL, NULL, 0}, 104762306a36Sopenharmony_ci}; 104862306a36Sopenharmony_ci 104962306a36Sopenharmony_ci/* Set up AEM1 sensor attrs */ 105062306a36Sopenharmony_cistatic int aem1_find_sensors(struct aem_data *data) 105162306a36Sopenharmony_ci{ 105262306a36Sopenharmony_ci return aem_register_sensors(data, aem1_ro_sensors, aem1_rw_sensors); 105362306a36Sopenharmony_ci} 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci/* Set up AEM2 sensor attrs */ 105662306a36Sopenharmony_cistatic int aem2_find_sensors(struct aem_data *data) 105762306a36Sopenharmony_ci{ 105862306a36Sopenharmony_ci return aem_register_sensors(data, aem2_ro_sensors, aem2_rw_sensors); 105962306a36Sopenharmony_ci} 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ci/* Module init/exit routines */ 106262306a36Sopenharmony_ci 106362306a36Sopenharmony_cistatic int __init aem_init(void) 106462306a36Sopenharmony_ci{ 106562306a36Sopenharmony_ci int res; 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci res = driver_register(&aem_driver.driver); 106862306a36Sopenharmony_ci if (res) { 106962306a36Sopenharmony_ci pr_err("Can't register aem driver\n"); 107062306a36Sopenharmony_ci return res; 107162306a36Sopenharmony_ci } 107262306a36Sopenharmony_ci 107362306a36Sopenharmony_ci res = ipmi_smi_watcher_register(&driver_data.bmc_events); 107462306a36Sopenharmony_ci if (res) 107562306a36Sopenharmony_ci goto ipmi_reg_err; 107662306a36Sopenharmony_ci return 0; 107762306a36Sopenharmony_ci 107862306a36Sopenharmony_ciipmi_reg_err: 107962306a36Sopenharmony_ci driver_unregister(&aem_driver.driver); 108062306a36Sopenharmony_ci return res; 108162306a36Sopenharmony_ci 108262306a36Sopenharmony_ci} 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_cistatic void __exit aem_exit(void) 108562306a36Sopenharmony_ci{ 108662306a36Sopenharmony_ci struct aem_data *p1, *next1; 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci ipmi_smi_watcher_unregister(&driver_data.bmc_events); 108962306a36Sopenharmony_ci driver_unregister(&aem_driver.driver); 109062306a36Sopenharmony_ci list_for_each_entry_safe(p1, next1, &driver_data.aem_devices, list) 109162306a36Sopenharmony_ci aem_delete(p1); 109262306a36Sopenharmony_ci} 109362306a36Sopenharmony_ci 109462306a36Sopenharmony_ciMODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>"); 109562306a36Sopenharmony_ciMODULE_DESCRIPTION("IBM AEM power/temp/energy sensor driver"); 109662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_cimodule_init(aem_init); 109962306a36Sopenharmony_cimodule_exit(aem_exit); 110062306a36Sopenharmony_ci 110162306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3350-*"); 110262306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3550-*"); 110362306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3650-*"); 110462306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3655-*"); 110562306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3755-*"); 110662306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBM3850M2/x3950M2-*"); 110762306a36Sopenharmony_ciMODULE_ALIAS("dmi:bvnIBM:*:pnIBMBladeHC10-*"); 1108