xref: /openbmc/linux/drivers/hwmon/ibmpex.c (revision ff92136131c66a51b820798067e53f05a8f36a24)
157c7c3a0SDarrick J. Wong /*
257c7c3a0SDarrick J. Wong  * A hwmon driver for the IBM PowerExecutive temperature/power sensors
357c7c3a0SDarrick J. Wong  * Copyright (C) 2007 IBM
457c7c3a0SDarrick J. Wong  *
557c7c3a0SDarrick J. Wong  * Author: Darrick J. Wong <djwong@us.ibm.com>
657c7c3a0SDarrick J. Wong  *
757c7c3a0SDarrick J. Wong  * This program is free software; you can redistribute it and/or modify
857c7c3a0SDarrick J. Wong  * it under the terms of the GNU General Public License as published by
957c7c3a0SDarrick J. Wong  * the Free Software Foundation; either version 2 of the License, or
1057c7c3a0SDarrick J. Wong  * (at your option) any later version.
1157c7c3a0SDarrick J. Wong  *
1257c7c3a0SDarrick J. Wong  * This program is distributed in the hope that it will be useful,
1357c7c3a0SDarrick J. Wong  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1457c7c3a0SDarrick J. Wong  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1557c7c3a0SDarrick J. Wong  * GNU General Public License for more details.
1657c7c3a0SDarrick J. Wong  *
1757c7c3a0SDarrick J. Wong  * You should have received a copy of the GNU General Public License
1857c7c3a0SDarrick J. Wong  * along with this program; if not, write to the Free Software
1957c7c3a0SDarrick J. Wong  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
2057c7c3a0SDarrick J. Wong  */
2157c7c3a0SDarrick J. Wong 
2257c7c3a0SDarrick J. Wong #include <linux/ipmi.h>
2357c7c3a0SDarrick J. Wong #include <linux/module.h>
2457c7c3a0SDarrick J. Wong #include <linux/hwmon.h>
2557c7c3a0SDarrick J. Wong #include <linux/hwmon-sysfs.h>
2657c7c3a0SDarrick J. Wong #include <linux/jiffies.h>
2757c7c3a0SDarrick J. Wong #include <linux/mutex.h>
2857c7c3a0SDarrick J. Wong 
2957c7c3a0SDarrick J. Wong #define REFRESH_INTERVAL	(2 * HZ)
3057c7c3a0SDarrick J. Wong #define DRVNAME			"ibmpex"
3157c7c3a0SDarrick J. Wong 
3257c7c3a0SDarrick J. Wong #define PEX_GET_VERSION		1
3357c7c3a0SDarrick J. Wong #define PEX_GET_SENSOR_COUNT	2
3457c7c3a0SDarrick J. Wong #define PEX_GET_SENSOR_NAME	3
3557c7c3a0SDarrick J. Wong #define PEX_RESET_HIGH_LOW	4
3657c7c3a0SDarrick J. Wong #define PEX_GET_SENSOR_DATA	6
3757c7c3a0SDarrick J. Wong 
3857c7c3a0SDarrick J. Wong #define PEX_NET_FUNCTION	0x3A
3957c7c3a0SDarrick J. Wong #define PEX_COMMAND		0x3C
4057c7c3a0SDarrick J. Wong 
4157c7c3a0SDarrick J. Wong static inline u16 extract_value(const char *data, int offset)
4257c7c3a0SDarrick J. Wong {
4357c7c3a0SDarrick J. Wong 	return be16_to_cpup((u16 *)&data[offset]);
4457c7c3a0SDarrick J. Wong }
4557c7c3a0SDarrick J. Wong 
4657c7c3a0SDarrick J. Wong #define TEMP_SENSOR		1
4757c7c3a0SDarrick J. Wong #define POWER_SENSOR		2
4857c7c3a0SDarrick J. Wong 
4957c7c3a0SDarrick J. Wong #define PEX_SENSOR_TYPE_LEN	3
5057c7c3a0SDarrick J. Wong static u8 const power_sensor_sig[] = {0x70, 0x77, 0x72};
5157c7c3a0SDarrick J. Wong static u8 const temp_sensor_sig[]  = {0x74, 0x65, 0x6D};
5257c7c3a0SDarrick J. Wong 
5357c7c3a0SDarrick J. Wong #define PEX_MULT_LEN		2
5457c7c3a0SDarrick J. Wong static u8 const watt_sensor_sig[]  = {0x41, 0x43};
5557c7c3a0SDarrick J. Wong 
5657c7c3a0SDarrick J. Wong #define PEX_NUM_SENSOR_FUNCS	3
5757c7c3a0SDarrick J. Wong static char const * const power_sensor_name_templates[] = {
5857c7c3a0SDarrick J. Wong 	"%s%d_average",
5957c7c3a0SDarrick J. Wong 	"%s%d_average_lowest",
6057c7c3a0SDarrick J. Wong 	"%s%d_average_highest"
6157c7c3a0SDarrick J. Wong };
6257c7c3a0SDarrick J. Wong static char const * const temp_sensor_name_templates[] = {
6357c7c3a0SDarrick J. Wong 	"%s%d_input",
6457c7c3a0SDarrick J. Wong 	"%s%d_input_lowest",
6557c7c3a0SDarrick J. Wong 	"%s%d_input_highest"
6657c7c3a0SDarrick J. Wong };
6757c7c3a0SDarrick J. Wong 
6857c7c3a0SDarrick J. Wong static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data);
6957c7c3a0SDarrick J. Wong static void ibmpex_register_bmc(int iface, struct device *dev);
7057c7c3a0SDarrick J. Wong static void ibmpex_bmc_gone(int iface);
7157c7c3a0SDarrick J. Wong 
7257c7c3a0SDarrick J. Wong struct ibmpex_sensor_data {
7357c7c3a0SDarrick J. Wong 	int			in_use;
7457c7c3a0SDarrick J. Wong 	s16			values[PEX_NUM_SENSOR_FUNCS];
7557c7c3a0SDarrick J. Wong 	int			multiplier;
7657c7c3a0SDarrick J. Wong 
7757c7c3a0SDarrick J. Wong 	struct sensor_device_attribute_2	attr[PEX_NUM_SENSOR_FUNCS];
7857c7c3a0SDarrick J. Wong };
7957c7c3a0SDarrick J. Wong 
8057c7c3a0SDarrick J. Wong struct ibmpex_bmc_data {
8157c7c3a0SDarrick J. Wong 	struct list_head	list;
8257c7c3a0SDarrick J. Wong 	struct device		*hwmon_dev;
8357c7c3a0SDarrick J. Wong 	struct device		*bmc_device;
8457c7c3a0SDarrick J. Wong 	struct mutex		lock;
8557c7c3a0SDarrick J. Wong 	char			valid;
8657c7c3a0SDarrick J. Wong 	unsigned long		last_updated;	/* In jiffies */
8757c7c3a0SDarrick J. Wong 
8857c7c3a0SDarrick J. Wong 	struct ipmi_addr	address;
8957c7c3a0SDarrick J. Wong 	struct completion	read_complete;
9057c7c3a0SDarrick J. Wong 	ipmi_user_t		user;
9157c7c3a0SDarrick J. Wong 	int			interface;
9257c7c3a0SDarrick J. Wong 
9357c7c3a0SDarrick J. Wong 	struct kernel_ipmi_msg	tx_message;
9457c7c3a0SDarrick J. Wong 	unsigned char		tx_msg_data[IPMI_MAX_MSG_LENGTH];
9557c7c3a0SDarrick J. Wong 	long			tx_msgid;
9657c7c3a0SDarrick J. Wong 
9757c7c3a0SDarrick J. Wong 	unsigned char		rx_msg_data[IPMI_MAX_MSG_LENGTH];
9857c7c3a0SDarrick J. Wong 	unsigned long		rx_msg_len;
9957c7c3a0SDarrick J. Wong 	unsigned char		rx_result;
10057c7c3a0SDarrick J. Wong 	int			rx_recv_type;
10157c7c3a0SDarrick J. Wong 
10257c7c3a0SDarrick J. Wong 	unsigned char		sensor_major;
10357c7c3a0SDarrick J. Wong 	unsigned char		sensor_minor;
10457c7c3a0SDarrick J. Wong 
10557c7c3a0SDarrick J. Wong 	unsigned char		num_sensors;
10657c7c3a0SDarrick J. Wong 	struct ibmpex_sensor_data	*sensors;
10757c7c3a0SDarrick J. Wong };
10857c7c3a0SDarrick J. Wong 
10957c7c3a0SDarrick J. Wong struct ibmpex_driver_data {
11057c7c3a0SDarrick J. Wong 	struct list_head	bmc_data;
11157c7c3a0SDarrick J. Wong 	struct ipmi_smi_watcher	bmc_events;
11257c7c3a0SDarrick J. Wong 	struct ipmi_user_hndl	ipmi_hndlrs;
11357c7c3a0SDarrick J. Wong };
11457c7c3a0SDarrick J. Wong 
11557c7c3a0SDarrick J. Wong static struct ibmpex_driver_data driver_data = {
11657c7c3a0SDarrick J. Wong 	.bmc_data = LIST_HEAD_INIT(driver_data.bmc_data),
11757c7c3a0SDarrick J. Wong 	.bmc_events = {
11857c7c3a0SDarrick J. Wong 		.owner = THIS_MODULE,
11957c7c3a0SDarrick J. Wong 		.new_smi = ibmpex_register_bmc,
12057c7c3a0SDarrick J. Wong 		.smi_gone = ibmpex_bmc_gone,
12157c7c3a0SDarrick J. Wong 	},
12257c7c3a0SDarrick J. Wong 	.ipmi_hndlrs = {
12357c7c3a0SDarrick J. Wong 		.ipmi_recv_hndl = ibmpex_msg_handler,
12457c7c3a0SDarrick J. Wong 	},
12557c7c3a0SDarrick J. Wong };
12657c7c3a0SDarrick J. Wong 
12757c7c3a0SDarrick J. Wong static int ibmpex_send_message(struct ibmpex_bmc_data *data)
12857c7c3a0SDarrick J. Wong {
12957c7c3a0SDarrick J. Wong 	int err;
13057c7c3a0SDarrick J. Wong 
13157c7c3a0SDarrick J. Wong 	err = ipmi_validate_addr(&data->address, sizeof(data->address));
13257c7c3a0SDarrick J. Wong 	if (err)
13357c7c3a0SDarrick J. Wong 		goto out;
13457c7c3a0SDarrick J. Wong 
13557c7c3a0SDarrick J. Wong 	data->tx_msgid++;
13657c7c3a0SDarrick J. Wong 	err = ipmi_request_settime(data->user, &data->address, data->tx_msgid,
13757c7c3a0SDarrick J. Wong 				   &data->tx_message, data, 0, 0, 0);
13857c7c3a0SDarrick J. Wong 	if (err)
13957c7c3a0SDarrick J. Wong 		goto out1;
14057c7c3a0SDarrick J. Wong 
14157c7c3a0SDarrick J. Wong 	return 0;
14257c7c3a0SDarrick J. Wong out1:
1432ecb044eSDarrick J. Wong 	dev_err(data->bmc_device, "request_settime=%x\n", err);
14457c7c3a0SDarrick J. Wong 	return err;
14557c7c3a0SDarrick J. Wong out:
1462ecb044eSDarrick J. Wong 	dev_err(data->bmc_device, "validate_addr=%x\n", err);
14757c7c3a0SDarrick J. Wong 	return err;
14857c7c3a0SDarrick J. Wong }
14957c7c3a0SDarrick J. Wong 
15057c7c3a0SDarrick J. Wong static int ibmpex_ver_check(struct ibmpex_bmc_data *data)
15157c7c3a0SDarrick J. Wong {
15257c7c3a0SDarrick J. Wong 	data->tx_msg_data[0] = PEX_GET_VERSION;
15357c7c3a0SDarrick J. Wong 	data->tx_message.data_len = 1;
15457c7c3a0SDarrick J. Wong 	ibmpex_send_message(data);
15557c7c3a0SDarrick J. Wong 
15657c7c3a0SDarrick J. Wong 	wait_for_completion(&data->read_complete);
15757c7c3a0SDarrick J. Wong 
15857c7c3a0SDarrick J. Wong 	if (data->rx_result || data->rx_msg_len != 6)
15957c7c3a0SDarrick J. Wong 		return -ENOENT;
16057c7c3a0SDarrick J. Wong 
16157c7c3a0SDarrick J. Wong 	data->sensor_major = data->rx_msg_data[0];
16257c7c3a0SDarrick J. Wong 	data->sensor_minor = data->rx_msg_data[1];
16357c7c3a0SDarrick J. Wong 
1642ecb044eSDarrick J. Wong 	dev_info(data->bmc_device, "Found BMC with sensor interface "
16557c7c3a0SDarrick J. Wong 		 "v%d.%d %d-%02d-%02d on interface %d\n",
16657c7c3a0SDarrick J. Wong 		 data->sensor_major,
16757c7c3a0SDarrick J. Wong 		 data->sensor_minor,
16857c7c3a0SDarrick J. Wong 		 extract_value(data->rx_msg_data, 2),
16957c7c3a0SDarrick J. Wong 		 data->rx_msg_data[4],
17057c7c3a0SDarrick J. Wong 		 data->rx_msg_data[5],
17157c7c3a0SDarrick J. Wong 		 data->interface);
17257c7c3a0SDarrick J. Wong 
17357c7c3a0SDarrick J. Wong 	return 0;
17457c7c3a0SDarrick J. Wong }
17557c7c3a0SDarrick J. Wong 
17657c7c3a0SDarrick J. Wong static int ibmpex_query_sensor_count(struct ibmpex_bmc_data *data)
17757c7c3a0SDarrick J. Wong {
17857c7c3a0SDarrick J. Wong 	data->tx_msg_data[0] = PEX_GET_SENSOR_COUNT;
17957c7c3a0SDarrick J. Wong 	data->tx_message.data_len = 1;
18057c7c3a0SDarrick J. Wong 	ibmpex_send_message(data);
18157c7c3a0SDarrick J. Wong 
18257c7c3a0SDarrick J. Wong 	wait_for_completion(&data->read_complete);
18357c7c3a0SDarrick J. Wong 
18457c7c3a0SDarrick J. Wong 	if (data->rx_result || data->rx_msg_len != 1)
18557c7c3a0SDarrick J. Wong 		return -ENOENT;
18657c7c3a0SDarrick J. Wong 
18757c7c3a0SDarrick J. Wong 	return data->rx_msg_data[0];
18857c7c3a0SDarrick J. Wong }
18957c7c3a0SDarrick J. Wong 
19057c7c3a0SDarrick J. Wong static int ibmpex_query_sensor_name(struct ibmpex_bmc_data *data, int sensor)
19157c7c3a0SDarrick J. Wong {
19257c7c3a0SDarrick J. Wong 	data->tx_msg_data[0] = PEX_GET_SENSOR_NAME;
19357c7c3a0SDarrick J. Wong 	data->tx_msg_data[1] = sensor;
19457c7c3a0SDarrick J. Wong 	data->tx_message.data_len = 2;
19557c7c3a0SDarrick J. Wong 	ibmpex_send_message(data);
19657c7c3a0SDarrick J. Wong 
19757c7c3a0SDarrick J. Wong 	wait_for_completion(&data->read_complete);
19857c7c3a0SDarrick J. Wong 
19957c7c3a0SDarrick J. Wong 	if (data->rx_result || data->rx_msg_len < 1)
20057c7c3a0SDarrick J. Wong 		return -ENOENT;
20157c7c3a0SDarrick J. Wong 
20257c7c3a0SDarrick J. Wong 	return 0;
20357c7c3a0SDarrick J. Wong }
20457c7c3a0SDarrick J. Wong 
20557c7c3a0SDarrick J. Wong static int ibmpex_query_sensor_data(struct ibmpex_bmc_data *data, int sensor)
20657c7c3a0SDarrick J. Wong {
20757c7c3a0SDarrick J. Wong 	data->tx_msg_data[0] = PEX_GET_SENSOR_DATA;
20857c7c3a0SDarrick J. Wong 	data->tx_msg_data[1] = sensor;
20957c7c3a0SDarrick J. Wong 	data->tx_message.data_len = 2;
21057c7c3a0SDarrick J. Wong 	ibmpex_send_message(data);
21157c7c3a0SDarrick J. Wong 
21257c7c3a0SDarrick J. Wong 	wait_for_completion(&data->read_complete);
21357c7c3a0SDarrick J. Wong 
21457c7c3a0SDarrick J. Wong 	if (data->rx_result || data->rx_msg_len < 26) {
2152ecb044eSDarrick J. Wong 		dev_err(data->bmc_device, "Error reading sensor %d.\n",
21657c7c3a0SDarrick J. Wong 			sensor);
21757c7c3a0SDarrick J. Wong 		return -ENOENT;
21857c7c3a0SDarrick J. Wong 	}
21957c7c3a0SDarrick J. Wong 
22057c7c3a0SDarrick J. Wong 	return 0;
22157c7c3a0SDarrick J. Wong }
22257c7c3a0SDarrick J. Wong 
22357c7c3a0SDarrick J. Wong static int ibmpex_reset_high_low_data(struct ibmpex_bmc_data *data)
22457c7c3a0SDarrick J. Wong {
22557c7c3a0SDarrick J. Wong 	data->tx_msg_data[0] = PEX_RESET_HIGH_LOW;
22657c7c3a0SDarrick J. Wong 	data->tx_message.data_len = 1;
22757c7c3a0SDarrick J. Wong 	ibmpex_send_message(data);
22857c7c3a0SDarrick J. Wong 
22957c7c3a0SDarrick J. Wong 	wait_for_completion(&data->read_complete);
23057c7c3a0SDarrick J. Wong 
23157c7c3a0SDarrick J. Wong 	return 0;
23257c7c3a0SDarrick J. Wong }
23357c7c3a0SDarrick J. Wong 
23457c7c3a0SDarrick J. Wong static void ibmpex_update_device(struct ibmpex_bmc_data *data)
23557c7c3a0SDarrick J. Wong {
23657c7c3a0SDarrick J. Wong 	int i, err;
23757c7c3a0SDarrick J. Wong 
23857c7c3a0SDarrick J. Wong 	mutex_lock(&data->lock);
23957c7c3a0SDarrick J. Wong 	if (time_before(jiffies, data->last_updated + REFRESH_INTERVAL) &&
24057c7c3a0SDarrick J. Wong 	    data->valid)
24157c7c3a0SDarrick J. Wong 		goto out;
24257c7c3a0SDarrick J. Wong 
24357c7c3a0SDarrick J. Wong 	for (i = 0; i < data->num_sensors; i++) {
24457c7c3a0SDarrick J. Wong 		if (!data->sensors[i].in_use)
24557c7c3a0SDarrick J. Wong 			continue;
24657c7c3a0SDarrick J. Wong 		err = ibmpex_query_sensor_data(data, i);
24757c7c3a0SDarrick J. Wong 		if (err)
24857c7c3a0SDarrick J. Wong 			continue;
24957c7c3a0SDarrick J. Wong 		data->sensors[i].values[0] =
25057c7c3a0SDarrick J. Wong 			extract_value(data->rx_msg_data, 16);
25157c7c3a0SDarrick J. Wong 		data->sensors[i].values[1] =
25257c7c3a0SDarrick J. Wong 			extract_value(data->rx_msg_data, 18);
25357c7c3a0SDarrick J. Wong 		data->sensors[i].values[2] =
25457c7c3a0SDarrick J. Wong 			extract_value(data->rx_msg_data, 20);
25557c7c3a0SDarrick J. Wong 	}
25657c7c3a0SDarrick J. Wong 
25757c7c3a0SDarrick J. Wong 	data->last_updated = jiffies;
25857c7c3a0SDarrick J. Wong 	data->valid = 1;
25957c7c3a0SDarrick J. Wong 
26057c7c3a0SDarrick J. Wong out:
26157c7c3a0SDarrick J. Wong 	mutex_unlock(&data->lock);
26257c7c3a0SDarrick J. Wong }
26357c7c3a0SDarrick J. Wong 
26457c7c3a0SDarrick J. Wong static struct ibmpex_bmc_data *get_bmc_data(int iface)
26557c7c3a0SDarrick J. Wong {
26657c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *p, *next;
26757c7c3a0SDarrick J. Wong 
26857c7c3a0SDarrick J. Wong 	list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
26957c7c3a0SDarrick J. Wong 		if (p->interface == iface)
27057c7c3a0SDarrick J. Wong 			return p;
27157c7c3a0SDarrick J. Wong 
27257c7c3a0SDarrick J. Wong 	return NULL;
27357c7c3a0SDarrick J. Wong }
27457c7c3a0SDarrick J. Wong 
27557c7c3a0SDarrick J. Wong static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
27657c7c3a0SDarrick J. Wong 			 char *buf)
27757c7c3a0SDarrick J. Wong {
27857c7c3a0SDarrick J. Wong 	return sprintf(buf, "%s\n", DRVNAME);
27957c7c3a0SDarrick J. Wong }
28057c7c3a0SDarrick J. Wong static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0);
28157c7c3a0SDarrick J. Wong 
28257c7c3a0SDarrick J. Wong static ssize_t ibmpex_show_sensor(struct device *dev,
28357c7c3a0SDarrick J. Wong 				  struct device_attribute *devattr,
28457c7c3a0SDarrick J. Wong 				  char *buf)
28557c7c3a0SDarrick J. Wong {
28657c7c3a0SDarrick J. Wong 	struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
28757c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *data = dev_get_drvdata(dev);
28857c7c3a0SDarrick J. Wong 	int mult = data->sensors[attr->index].multiplier;
28957c7c3a0SDarrick J. Wong 	ibmpex_update_device(data);
29057c7c3a0SDarrick J. Wong 
29157c7c3a0SDarrick J. Wong 	return sprintf(buf, "%d\n",
29257c7c3a0SDarrick J. Wong 		       data->sensors[attr->index].values[attr->nr] * mult);
29357c7c3a0SDarrick J. Wong }
29457c7c3a0SDarrick J. Wong 
29557c7c3a0SDarrick J. Wong static ssize_t ibmpex_reset_high_low(struct device *dev,
29657c7c3a0SDarrick J. Wong 				     struct device_attribute *devattr,
29757c7c3a0SDarrick J. Wong 				     const char *buf,
29857c7c3a0SDarrick J. Wong 				     size_t count)
29957c7c3a0SDarrick J. Wong {
30057c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *data = dev_get_drvdata(dev);
30157c7c3a0SDarrick J. Wong 
30257c7c3a0SDarrick J. Wong 	ibmpex_reset_high_low_data(data);
30357c7c3a0SDarrick J. Wong 
30457c7c3a0SDarrick J. Wong 	return count;
30557c7c3a0SDarrick J. Wong }
30657c7c3a0SDarrick J. Wong 
30757c7c3a0SDarrick J. Wong static SENSOR_DEVICE_ATTR(reset_high_low, S_IWUSR, NULL,
30857c7c3a0SDarrick J. Wong 			  ibmpex_reset_high_low, 0);
30957c7c3a0SDarrick J. Wong 
31057c7c3a0SDarrick J. Wong static int is_power_sensor(const char *sensor_id, int len)
31157c7c3a0SDarrick J. Wong {
31257c7c3a0SDarrick J. Wong 	if (len < PEX_SENSOR_TYPE_LEN)
31357c7c3a0SDarrick J. Wong 		return 0;
31457c7c3a0SDarrick J. Wong 
31557c7c3a0SDarrick J. Wong 	if (!memcmp(sensor_id, power_sensor_sig, PEX_SENSOR_TYPE_LEN))
31657c7c3a0SDarrick J. Wong 		return 1;
31757c7c3a0SDarrick J. Wong 	return 0;
31857c7c3a0SDarrick J. Wong }
31957c7c3a0SDarrick J. Wong 
32057c7c3a0SDarrick J. Wong static int is_temp_sensor(const char *sensor_id, int len)
32157c7c3a0SDarrick J. Wong {
32257c7c3a0SDarrick J. Wong 	if (len < PEX_SENSOR_TYPE_LEN)
32357c7c3a0SDarrick J. Wong 		return 0;
32457c7c3a0SDarrick J. Wong 
32557c7c3a0SDarrick J. Wong 	if (!memcmp(sensor_id, temp_sensor_sig, PEX_SENSOR_TYPE_LEN))
32657c7c3a0SDarrick J. Wong 		return 1;
32757c7c3a0SDarrick J. Wong 	return 0;
32857c7c3a0SDarrick J. Wong }
32957c7c3a0SDarrick J. Wong 
330df9cb033SDarrick J. Wong static int power_sensor_multiplier(struct ibmpex_bmc_data *data,
331df9cb033SDarrick J. Wong 				   const char *sensor_id, int len)
33257c7c3a0SDarrick J. Wong {
33357c7c3a0SDarrick J. Wong 	int i;
33457c7c3a0SDarrick J. Wong 
335df9cb033SDarrick J. Wong 	if (data->sensor_major == 2)
336df9cb033SDarrick J. Wong 		return 1000000;
337df9cb033SDarrick J. Wong 
33857c7c3a0SDarrick J. Wong 	for (i = PEX_SENSOR_TYPE_LEN; i < len - 1; i++)
33957c7c3a0SDarrick J. Wong 		if (!memcmp(&sensor_id[i], watt_sensor_sig, PEX_MULT_LEN))
34057c7c3a0SDarrick J. Wong 			return 1000000;
34157c7c3a0SDarrick J. Wong 
34257c7c3a0SDarrick J. Wong 	return 100000;
34357c7c3a0SDarrick J. Wong }
34457c7c3a0SDarrick J. Wong 
34557c7c3a0SDarrick J. Wong static int create_sensor(struct ibmpex_bmc_data *data, int type,
34657c7c3a0SDarrick J. Wong 			 int counter, int sensor, int func)
34757c7c3a0SDarrick J. Wong {
34857c7c3a0SDarrick J. Wong 	int err;
34957c7c3a0SDarrick J. Wong 	char *n;
35057c7c3a0SDarrick J. Wong 
35157c7c3a0SDarrick J. Wong 	n = kmalloc(32, GFP_KERNEL);
35257c7c3a0SDarrick J. Wong 	if (!n)
35357c7c3a0SDarrick J. Wong 		return -ENOMEM;
35457c7c3a0SDarrick J. Wong 
35557c7c3a0SDarrick J. Wong 	if (type == TEMP_SENSOR)
35657c7c3a0SDarrick J. Wong 		sprintf(n, temp_sensor_name_templates[func], "temp", counter);
35757c7c3a0SDarrick J. Wong 	else if (type == POWER_SENSOR)
35857c7c3a0SDarrick J. Wong 		sprintf(n, power_sensor_name_templates[func], "power", counter);
35957c7c3a0SDarrick J. Wong 
36057c7c3a0SDarrick J. Wong 	data->sensors[sensor].attr[func].dev_attr.attr.name = n;
36157c7c3a0SDarrick J. Wong 	data->sensors[sensor].attr[func].dev_attr.attr.mode = S_IRUGO;
36257c7c3a0SDarrick J. Wong 	data->sensors[sensor].attr[func].dev_attr.show = ibmpex_show_sensor;
36357c7c3a0SDarrick J. Wong 	data->sensors[sensor].attr[func].index = sensor;
36457c7c3a0SDarrick J. Wong 	data->sensors[sensor].attr[func].nr = func;
36557c7c3a0SDarrick J. Wong 
36657c7c3a0SDarrick J. Wong 	err = device_create_file(data->bmc_device,
36757c7c3a0SDarrick J. Wong 				 &data->sensors[sensor].attr[func].dev_attr);
36857c7c3a0SDarrick J. Wong 	if (err) {
36957c7c3a0SDarrick J. Wong 		data->sensors[sensor].attr[func].dev_attr.attr.name = NULL;
37057c7c3a0SDarrick J. Wong 		kfree(n);
37157c7c3a0SDarrick J. Wong 		return err;
37257c7c3a0SDarrick J. Wong 	}
37357c7c3a0SDarrick J. Wong 
37457c7c3a0SDarrick J. Wong 	return 0;
37557c7c3a0SDarrick J. Wong }
37657c7c3a0SDarrick J. Wong 
37757c7c3a0SDarrick J. Wong static int ibmpex_find_sensors(struct ibmpex_bmc_data *data)
37857c7c3a0SDarrick J. Wong {
37957c7c3a0SDarrick J. Wong 	int i, j, err;
38057c7c3a0SDarrick J. Wong 	int sensor_type;
38157c7c3a0SDarrick J. Wong 	int sensor_counter;
38257c7c3a0SDarrick J. Wong 	int num_power = 0;
38357c7c3a0SDarrick J. Wong 	int num_temp = 0;
38457c7c3a0SDarrick J. Wong 
38557c7c3a0SDarrick J. Wong 	err = ibmpex_query_sensor_count(data);
38657c7c3a0SDarrick J. Wong 	if (err <= 0)
38757c7c3a0SDarrick J. Wong 		return -ENOENT;
38857c7c3a0SDarrick J. Wong 	data->num_sensors = err;
38957c7c3a0SDarrick J. Wong 
39057c7c3a0SDarrick J. Wong 	data->sensors = kzalloc(data->num_sensors * sizeof(*data->sensors),
39157c7c3a0SDarrick J. Wong 				GFP_KERNEL);
39257c7c3a0SDarrick J. Wong 	if (!data->sensors)
39357c7c3a0SDarrick J. Wong 		return -ENOMEM;
39457c7c3a0SDarrick J. Wong 
39557c7c3a0SDarrick J. Wong 	for (i = 0; i < data->num_sensors; i++) {
39657c7c3a0SDarrick J. Wong 		err = ibmpex_query_sensor_name(data, i);
39757c7c3a0SDarrick J. Wong 		if (err)
39857c7c3a0SDarrick J. Wong 			continue;
39957c7c3a0SDarrick J. Wong 
40057c7c3a0SDarrick J. Wong 		if (is_power_sensor(data->rx_msg_data, data->rx_msg_len)) {
40157c7c3a0SDarrick J. Wong 			sensor_type = POWER_SENSOR;
40257c7c3a0SDarrick J. Wong 			num_power++;
40357c7c3a0SDarrick J. Wong 			sensor_counter = num_power;
40457c7c3a0SDarrick J. Wong 			data->sensors[i].multiplier =
405df9cb033SDarrick J. Wong 				power_sensor_multiplier(data,
406df9cb033SDarrick J. Wong 							data->rx_msg_data,
40757c7c3a0SDarrick J. Wong 							data->rx_msg_len);
40857c7c3a0SDarrick J. Wong 		} else if (is_temp_sensor(data->rx_msg_data,
40957c7c3a0SDarrick J. Wong 					  data->rx_msg_len)) {
41057c7c3a0SDarrick J. Wong 			sensor_type = TEMP_SENSOR;
41157c7c3a0SDarrick J. Wong 			num_temp++;
41257c7c3a0SDarrick J. Wong 			sensor_counter = num_temp;
413ffda6857SDarrick J. Wong 			data->sensors[i].multiplier = 1000;
41457c7c3a0SDarrick J. Wong 		} else
41557c7c3a0SDarrick J. Wong 			continue;
41657c7c3a0SDarrick J. Wong 
41757c7c3a0SDarrick J. Wong 		data->sensors[i].in_use = 1;
41857c7c3a0SDarrick J. Wong 
41957c7c3a0SDarrick J. Wong 		/* Create attributes */
42057c7c3a0SDarrick J. Wong 		for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
42157c7c3a0SDarrick J. Wong 			err = create_sensor(data, sensor_type, sensor_counter,
42257c7c3a0SDarrick J. Wong 					    i, j);
42357c7c3a0SDarrick J. Wong 			if (err)
42457c7c3a0SDarrick J. Wong 				goto exit_remove;
42557c7c3a0SDarrick J. Wong 		}
42657c7c3a0SDarrick J. Wong 	}
42757c7c3a0SDarrick J. Wong 
42857c7c3a0SDarrick J. Wong 	err = device_create_file(data->bmc_device,
42957c7c3a0SDarrick J. Wong 			&sensor_dev_attr_reset_high_low.dev_attr);
43057c7c3a0SDarrick J. Wong 	if (err)
43157c7c3a0SDarrick J. Wong 		goto exit_remove;
43257c7c3a0SDarrick J. Wong 
43357c7c3a0SDarrick J. Wong 	err = device_create_file(data->bmc_device,
43457c7c3a0SDarrick J. Wong 			&sensor_dev_attr_name.dev_attr);
43557c7c3a0SDarrick J. Wong 	if (err)
43657c7c3a0SDarrick J. Wong 		goto exit_remove;
43757c7c3a0SDarrick J. Wong 
43857c7c3a0SDarrick J. Wong 	return 0;
43957c7c3a0SDarrick J. Wong 
44057c7c3a0SDarrick J. Wong exit_remove:
44157c7c3a0SDarrick J. Wong 	device_remove_file(data->bmc_device,
44257c7c3a0SDarrick J. Wong 			   &sensor_dev_attr_reset_high_low.dev_attr);
44357c7c3a0SDarrick J. Wong 	device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
44457c7c3a0SDarrick J. Wong 	for (i = 0; i < data->num_sensors; i++)
44557c7c3a0SDarrick J. Wong 		for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
44657c7c3a0SDarrick J. Wong 			if (!data->sensors[i].attr[j].dev_attr.attr.name)
44757c7c3a0SDarrick J. Wong 				continue;
44857c7c3a0SDarrick J. Wong 			device_remove_file(data->bmc_device,
44957c7c3a0SDarrick J. Wong 				&data->sensors[i].attr[j].dev_attr);
45057c7c3a0SDarrick J. Wong 			kfree(data->sensors[i].attr[j].dev_attr.attr.name);
45157c7c3a0SDarrick J. Wong 		}
45257c7c3a0SDarrick J. Wong 
45357c7c3a0SDarrick J. Wong 	kfree(data->sensors);
45457c7c3a0SDarrick J. Wong 	return err;
45557c7c3a0SDarrick J. Wong }
45657c7c3a0SDarrick J. Wong 
45757c7c3a0SDarrick J. Wong static void ibmpex_register_bmc(int iface, struct device *dev)
45857c7c3a0SDarrick J. Wong {
45957c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *data;
46057c7c3a0SDarrick J. Wong 	int err;
46157c7c3a0SDarrick J. Wong 
46257c7c3a0SDarrick J. Wong 	data = kzalloc(sizeof(*data), GFP_KERNEL);
46357c7c3a0SDarrick J. Wong 	if (!data) {
4642ecb044eSDarrick J. Wong 		dev_err(dev, "Insufficient memory for BMC interface.\n");
46557c7c3a0SDarrick J. Wong 		return;
46657c7c3a0SDarrick J. Wong 	}
46757c7c3a0SDarrick J. Wong 
46857c7c3a0SDarrick J. Wong 	data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
46957c7c3a0SDarrick J. Wong 	data->address.channel = IPMI_BMC_CHANNEL;
47057c7c3a0SDarrick J. Wong 	data->address.data[0] = 0;
47157c7c3a0SDarrick J. Wong 	data->interface = iface;
47257c7c3a0SDarrick J. Wong 	data->bmc_device = dev;
47357c7c3a0SDarrick J. Wong 
47457c7c3a0SDarrick J. Wong 	/* Create IPMI messaging interface user */
47557c7c3a0SDarrick J. Wong 	err = ipmi_create_user(data->interface, &driver_data.ipmi_hndlrs,
47657c7c3a0SDarrick J. Wong 			       data, &data->user);
47757c7c3a0SDarrick J. Wong 	if (err < 0) {
4782ecb044eSDarrick J. Wong 		dev_err(dev, "Unable to register user with IPMI "
4792ecb044eSDarrick J. Wong 			"interface %d\n", data->interface);
48057c7c3a0SDarrick J. Wong 		goto out;
48157c7c3a0SDarrick J. Wong 	}
48257c7c3a0SDarrick J. Wong 
48357c7c3a0SDarrick J. Wong 	mutex_init(&data->lock);
48457c7c3a0SDarrick J. Wong 
48557c7c3a0SDarrick J. Wong 	/* Initialize message */
48657c7c3a0SDarrick J. Wong 	data->tx_msgid = 0;
48757c7c3a0SDarrick J. Wong 	init_completion(&data->read_complete);
48857c7c3a0SDarrick J. Wong 	data->tx_message.netfn = PEX_NET_FUNCTION;
48957c7c3a0SDarrick J. Wong 	data->tx_message.cmd = PEX_COMMAND;
49057c7c3a0SDarrick J. Wong 	data->tx_message.data = data->tx_msg_data;
49157c7c3a0SDarrick J. Wong 
49257c7c3a0SDarrick J. Wong 	/* Does this BMC support PowerExecutive? */
49357c7c3a0SDarrick J. Wong 	err = ibmpex_ver_check(data);
49457c7c3a0SDarrick J. Wong 	if (err)
49557c7c3a0SDarrick J. Wong 		goto out_user;
49657c7c3a0SDarrick J. Wong 
49757c7c3a0SDarrick J. Wong 	/* Register the BMC as a HWMON class device */
49857c7c3a0SDarrick J. Wong 	data->hwmon_dev = hwmon_device_register(data->bmc_device);
49957c7c3a0SDarrick J. Wong 
50057c7c3a0SDarrick J. Wong 	if (IS_ERR(data->hwmon_dev)) {
5012ecb044eSDarrick J. Wong 		dev_err(data->bmc_device, "Unable to register hwmon "
5022ecb044eSDarrick J. Wong 			"device for IPMI interface %d\n",
50357c7c3a0SDarrick J. Wong 			data->interface);
5044cfdbe7fSDarrick J. Wong 		goto out_user;
50557c7c3a0SDarrick J. Wong 	}
50657c7c3a0SDarrick J. Wong 
50757c7c3a0SDarrick J. Wong 	/* finally add the new bmc data to the bmc data list */
50857c7c3a0SDarrick J. Wong 	dev_set_drvdata(dev, data);
50957c7c3a0SDarrick J. Wong 	list_add_tail(&data->list, &driver_data.bmc_data);
51057c7c3a0SDarrick J. Wong 
51157c7c3a0SDarrick J. Wong 	/* Now go find all the sensors */
51257c7c3a0SDarrick J. Wong 	err = ibmpex_find_sensors(data);
51357c7c3a0SDarrick J. Wong 	if (err) {
5142ecb044eSDarrick J. Wong 		dev_err(data->bmc_device, "Error %d finding sensors\n", err);
51557c7c3a0SDarrick J. Wong 		goto out_register;
51657c7c3a0SDarrick J. Wong 	}
51757c7c3a0SDarrick J. Wong 
51857c7c3a0SDarrick J. Wong 	return;
51957c7c3a0SDarrick J. Wong 
52057c7c3a0SDarrick J. Wong out_register:
52157c7c3a0SDarrick J. Wong 	hwmon_device_unregister(data->hwmon_dev);
52257c7c3a0SDarrick J. Wong out_user:
52357c7c3a0SDarrick J. Wong 	ipmi_destroy_user(data->user);
52457c7c3a0SDarrick J. Wong out:
52557c7c3a0SDarrick J. Wong 	kfree(data);
52657c7c3a0SDarrick J. Wong }
52757c7c3a0SDarrick J. Wong 
52857c7c3a0SDarrick J. Wong static void ibmpex_bmc_delete(struct ibmpex_bmc_data *data)
52957c7c3a0SDarrick J. Wong {
53057c7c3a0SDarrick J. Wong 	int i, j;
53157c7c3a0SDarrick J. Wong 
53257c7c3a0SDarrick J. Wong 	device_remove_file(data->bmc_device,
53357c7c3a0SDarrick J. Wong 			   &sensor_dev_attr_reset_high_low.dev_attr);
53457c7c3a0SDarrick J. Wong 	device_remove_file(data->bmc_device, &sensor_dev_attr_name.dev_attr);
53557c7c3a0SDarrick J. Wong 	for (i = 0; i < data->num_sensors; i++)
53657c7c3a0SDarrick J. Wong 		for (j = 0; j < PEX_NUM_SENSOR_FUNCS; j++) {
53757c7c3a0SDarrick J. Wong 			if (!data->sensors[i].attr[j].dev_attr.attr.name)
53857c7c3a0SDarrick J. Wong 				continue;
53957c7c3a0SDarrick J. Wong 			device_remove_file(data->bmc_device,
54057c7c3a0SDarrick J. Wong 				&data->sensors[i].attr[j].dev_attr);
54157c7c3a0SDarrick J. Wong 			kfree(data->sensors[i].attr[j].dev_attr.attr.name);
54257c7c3a0SDarrick J. Wong 		}
54357c7c3a0SDarrick J. Wong 
54457c7c3a0SDarrick J. Wong 	list_del(&data->list);
54557c7c3a0SDarrick J. Wong 	dev_set_drvdata(data->bmc_device, NULL);
54657c7c3a0SDarrick J. Wong 	hwmon_device_unregister(data->hwmon_dev);
54757c7c3a0SDarrick J. Wong 	ipmi_destroy_user(data->user);
54857c7c3a0SDarrick J. Wong 	kfree(data->sensors);
54957c7c3a0SDarrick J. Wong 	kfree(data);
55057c7c3a0SDarrick J. Wong }
55157c7c3a0SDarrick J. Wong 
55257c7c3a0SDarrick J. Wong static void ibmpex_bmc_gone(int iface)
55357c7c3a0SDarrick J. Wong {
55457c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *data = get_bmc_data(iface);
55557c7c3a0SDarrick J. Wong 
55657c7c3a0SDarrick J. Wong 	if (!data)
55757c7c3a0SDarrick J. Wong 		return;
55857c7c3a0SDarrick J. Wong 
55957c7c3a0SDarrick J. Wong 	ibmpex_bmc_delete(data);
56057c7c3a0SDarrick J. Wong }
56157c7c3a0SDarrick J. Wong 
56257c7c3a0SDarrick J. Wong static void ibmpex_msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
56357c7c3a0SDarrick J. Wong {
56457c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *data = (struct ibmpex_bmc_data *)user_msg_data;
56557c7c3a0SDarrick J. Wong 
56657c7c3a0SDarrick J. Wong 	if (msg->msgid != data->tx_msgid) {
5672ecb044eSDarrick J. Wong 		dev_err(data->bmc_device, "Mismatch between received msgid "
5682ecb044eSDarrick J. Wong 			"(%02x) and transmitted msgid (%02x)!\n",
56957c7c3a0SDarrick J. Wong 			(int)msg->msgid,
57057c7c3a0SDarrick J. Wong 			(int)data->tx_msgid);
57157c7c3a0SDarrick J. Wong 		ipmi_free_recv_msg(msg);
57257c7c3a0SDarrick J. Wong 		return;
57357c7c3a0SDarrick J. Wong 	}
57457c7c3a0SDarrick J. Wong 
57557c7c3a0SDarrick J. Wong 	data->rx_recv_type = msg->recv_type;
57657c7c3a0SDarrick J. Wong 	if (msg->msg.data_len > 0)
57757c7c3a0SDarrick J. Wong 		data->rx_result = msg->msg.data[0];
57857c7c3a0SDarrick J. Wong 	else
57957c7c3a0SDarrick J. Wong 		data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE;
58057c7c3a0SDarrick J. Wong 
58157c7c3a0SDarrick J. Wong 	if (msg->msg.data_len > 1) {
58257c7c3a0SDarrick J. Wong 		data->rx_msg_len = msg->msg.data_len - 1;
58357c7c3a0SDarrick J. Wong 		memcpy(data->rx_msg_data, msg->msg.data + 1, data->rx_msg_len);
58457c7c3a0SDarrick J. Wong 	} else
58557c7c3a0SDarrick J. Wong 		data->rx_msg_len = 0;
58657c7c3a0SDarrick J. Wong 
58757c7c3a0SDarrick J. Wong 	ipmi_free_recv_msg(msg);
58857c7c3a0SDarrick J. Wong 	complete(&data->read_complete);
58957c7c3a0SDarrick J. Wong }
59057c7c3a0SDarrick J. Wong 
59157c7c3a0SDarrick J. Wong static int __init ibmpex_init(void)
59257c7c3a0SDarrick J. Wong {
59357c7c3a0SDarrick J. Wong 	return ipmi_smi_watcher_register(&driver_data.bmc_events);
59457c7c3a0SDarrick J. Wong }
59557c7c3a0SDarrick J. Wong 
59657c7c3a0SDarrick J. Wong static void __exit ibmpex_exit(void)
59757c7c3a0SDarrick J. Wong {
59857c7c3a0SDarrick J. Wong 	struct ibmpex_bmc_data *p, *next;
59957c7c3a0SDarrick J. Wong 
60057c7c3a0SDarrick J. Wong 	ipmi_smi_watcher_unregister(&driver_data.bmc_events);
60157c7c3a0SDarrick J. Wong 	list_for_each_entry_safe(p, next, &driver_data.bmc_data, list)
60257c7c3a0SDarrick J. Wong 		ibmpex_bmc_delete(p);
60357c7c3a0SDarrick J. Wong }
60457c7c3a0SDarrick J. Wong 
60557c7c3a0SDarrick J. Wong MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>");
60657c7c3a0SDarrick J. Wong MODULE_DESCRIPTION("IBM PowerExecutive power/temperature sensor driver");
60757c7c3a0SDarrick J. Wong MODULE_LICENSE("GPL");
60857c7c3a0SDarrick J. Wong 
60957c7c3a0SDarrick J. Wong module_init(ibmpex_init);
61057c7c3a0SDarrick J. Wong module_exit(ibmpex_exit);
611*ff921361SDarrick J. Wong 
612*ff921361SDarrick J. Wong MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3350-*");
613*ff921361SDarrick J. Wong MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3550-*");
614*ff921361SDarrick J. Wong MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3650-*");
615*ff921361SDarrick J. Wong MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3655-*");
616*ff921361SDarrick J. Wong MODULE_ALIAS("dmi:bvnIBM:*:pnIBMSystemx3755-*");
617