/* * fb_panther_plus.c - Driver for Panther+ microserver * * Copyright 2014-present Facebook. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ //#define DEBUG #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define PP_DEBUG(fmt, ...) do { \ printk(KERN_DEBUG "%s:%d " fmt "\n", \ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ } while (0) #else /* !DEBUG */ #define PP_DEBUG(fmt, ...) #endif static const unsigned short normal_i2c[] = { 0x40, I2C_CLIENT_END }; /* * Insmod parameters */ I2C_CLIENT_INSMOD_1(panther_plus); /* * Driver data (common to all clients) */ static const struct i2c_device_id panther_plus_id[] = { { "fb_panther_plus", panther_plus }, { }, }; MODULE_DEVICE_TABLE(i2c, panther_plus_id); struct panther_plus_data { struct device *hwmon_dev; struct mutex update_lock; }; // Identifies the sysfs attribute panther_plus_show is requesting. enum { PANTHER_PLUS_SYSFS_CPU_TEMP, PANTHER_PLUS_SYSFS_DIMM_TEMP, PANTHER_PLUS_SYSFS_GPIO_INPUTS, PANTHER_PLUS_SYSFS_SMS_KCS, PANTHER_PLUS_SYSFS_ALERT_CONTROL, PANTHER_PLUS_SYSFS_ALERT_STATUS, PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER, PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER, PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID, PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID, PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID, }; // Function Block ID identifiers. enum panther_plus_fbid_en { PANTHER_PLUS_FBID_IPMI_SMS_KCS = 0x0, PANTHER_PLUS_FBID_GPIO_INPUTS = 0xd, PANTHER_PLUS_FBID_READ_REGISTER = 0x10, PANTHER_PLUS_FBID_ALERT_CONTROL = 0xFD, PANTHER_PLUS_FBID_ALERT_STATUS = 0xFE, PANTHER_PLUS_FBID_DISCOVERY = 0xFF, }; static inline void panther_plus_make_read(struct i2c_msg *msg, u8 addr, u8 *buf, int len) { msg->addr = addr; msg->flags = I2C_M_RD; msg->buf = buf; msg->len = len; } static inline void panther_plus_make_write(struct i2c_msg *msg, u8 addr, u8 *buf, int len) { msg->addr = addr; msg->flags = 0; msg->buf = buf; msg->len = len; } static int panther_plus_fbid_io(struct i2c_client *client, enum panther_plus_fbid_en fbid, const u8 *write_buf, u8 write_len, u8 *read_buf, u8 read_len) { // The Intel uServer Module Management Interface Spec defines SMBus blocks, // but block sizes exceed the SMBus maximum block sizes // (32, see I2C_SMBUS_BLOCK_MAX). So we basically have to re-implement the // smbus functions with a larger max. struct i2c_msg msg[2]; u8 buf[255]; u8 buf_len; int rc; u8 num_msgs = 1; if (write_len + 1 > sizeof(buf)) { return -EINVAL; } /* first, write the FBID, followed by the write_buf if there is one */ buf[0] = fbid; buf_len = 1; if (write_buf) { memcpy(&buf[1], write_buf, write_len); buf_len += write_len; } panther_plus_make_write(&msg[0], client->addr, buf, buf_len); /* then, read */ if (read_buf) { panther_plus_make_read(&msg[1], client->addr, read_buf, read_len); num_msgs = 2; } rc = i2c_transfer(client->adapter, msg, num_msgs); if (rc < 0) { PP_DEBUG("Failed to read FBID: %d, error=%d", fbid, rc); return rc; } if (rc != num_msgs) { /* expect 2 */ PP_DEBUG("Unexpected rc (%d != %d) when reading FBID: %d", rc, num_msgs, fbid); return -EIO; } /* the first byte read should match fbid */ if (read_buf && read_buf[0] != fbid) { PP_DEBUG("Unexpected FBID returned (%d != %d)", read_buf[0], fbid); return -EIO; } return 0; } #define PP_GPIO_POWER_ON 0x1 #define PP_GPIO_PWRGD_P1V35 (0x1 << 1) #define PP_GPIO_RST_EAGE_N (0x1 << 2) #define PP_GPIO_FM_BIOS_POST_CMPLT_N (0x1 << 3) #define PP_GPIO_IERR_FPGA (0x1 << 4) #define PP_GPIO_AVN_PLD_PROCHOT_N (0x1 << 5) #define PP_GPIO_BUS1_ERROR (0x1 << 6) #define PP_GPIO_AVN_PLD_THERMTRIP_N (0x1 << 7) #define PP_GPIO_MCERR_FPGA (0x1 << 8) #define PP_GPIO_ERROR_AVN_2 (0x1 << 9) #define PP_GPIO_ERROR_AVN_1 (0x1 << 10) #define PP_GPIO_ERROR_AVN_0 (0x1 << 11) #define PP_GPIO_H_MEMHOT_CO_N (0x1 << 12) #define PP_GPIO_SLP_S45_N (0x1 << 13) #define PP_GPIO_PLTRST_FPGA_N (0x1 << 14) #define PP_GPIO_FPGA_GPI_PWD_FAIL (0x1 << 15) #define PP_GPIO_FPGA_GPI_NMI (0x1 << 16) #define PP_GPIO_GPI_VCCP_VRHOT_N (0x1 << 17) #define PP_GPIO_FPGA_GPI_TMP75_ALERT (0x1 << 18) #define PP_GPIO_LPC_CLKRUN_N (0x1 << 19) static int panther_plus_read_gpio_inputs_value( struct i2c_client *client, u32 *val) { int rc; u8 read_buf[5]; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_GPIO_INPUTS, NULL, 0, read_buf, sizeof(read_buf)); if (rc < 0) { return rc; } /* * expect receiving 5 bytes as: * 0xd 0x3 */ if (read_buf[1] != 0x3) { PP_DEBUG("Unexpected length %d != 3", read_buf[1]); return -EIO; } *val = read_buf[2] | (read_buf[3] << 8) | (read_buf[4] << 16); return 0; } static int panther_plus_is_in_post(struct i2c_client *client) { u32 val; int rc; rc = panther_plus_read_gpio_inputs_value(client, &val); if (rc < 0) { /* failed to read gpio, treat it as in post */ return 1; } /* if PP_GPIO_FM_BIOS_POST_CMPLT_N is set, post is _not_ done yet */ return (val & PP_GPIO_FM_BIOS_POST_CMPLT_N); } static int panther_plus_read_register(struct i2c_client *client, u8 reg_idx, u32 *reg_val) { u8 write_buf[2]; u8 read_buf[8]; int rc; /* need to send the register index for the reading */ write_buf[0] = 0x1; /* one byte */ write_buf[1] = reg_idx; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_READ_REGISTER, write_buf, sizeof(write_buf), read_buf, sizeof(read_buf)); if (rc < 0) { return -EIO; } /* * expect receiving 8 bytes as: * 0x10 0x6 LSB LSB+1 LSB+2 LSB+3 valid */ if (read_buf[1] != 0x6 || read_buf[2] != reg_idx || read_buf[7] != 0) { return -EIO; } *reg_val = read_buf[3] | (read_buf[4] << 8) | (read_buf[5] << 16) | (read_buf[6] << 24); PP_DEBUG("Read register %d: 0x%x", reg_idx, *reg_val); return 0; } #define PANTHER_PLUS_REG_SOC_TJMAX 0 #define PANTHER_PLUS_REG_SOC_RUNTIME 1 #define PANTHER_PLUS_REG_SOC_THERMAL_MARGIN 2 #define PANTHER_PLUS_REG_SOC_DIMM0_A_TEMP 3 #define PANTHER_PLUS_REG_SOC_DIMM0_B_TEMP 4 #define PANTHER_PLUS_REG_SOC_POWER_UNIT 5 #define PANTHER_PLUS_REG_SOC_POWER_CONSUMPTION 6 #define PANTHER_PLUS_REG_SOC_POWER_CALC 7 #define PANTHER_PLUS_REG_SOC_DIMM1_A_TEMP 8 #define PANTHER_PLUS_REG_SOC_DIMM1_B_TEMP 9 static int panther_plus_read_cpu_temp(struct i2c_client *client, char *ret) { int rc; u32 tjmax; u32 tmargin; int val; int temp; /* * make sure POST is done, accessing CPU temperature during POST phase could * confusing POST and make it hang */ if (panther_plus_is_in_post(client)) { return -EBUSY; } mdelay(10); /* first read Tjmax: register 0, bit[16-23] */ rc = panther_plus_read_register(client, PANTHER_PLUS_REG_SOC_TJMAX, &tjmax); if (rc < 0) { return rc; } mdelay(10); /* then, read the thermal margin */ rc = panther_plus_read_register(client, PANTHER_PLUS_REG_SOC_THERMAL_MARGIN, &tmargin); if (rc < 0) { return rc; } /* * thermal margin is 16b 2's complement value representing a number of 1/64 * degress centigrade. */ tmargin &= 0xFFFF; if ((tmargin & 0x8000)) { /* signed */ val = -((tmargin - 1) ^ 0xFFFF); } else { val = tmargin; } /* * now val holds the margin (a number of 1/64), add it to the Tjmax. * Times 1000 for lm-sensors. */ temp = ((tjmax >> 16) & 0xFF) * 1000 + val * 1000 / 64; return sprintf(ret, "%d\n", temp); } static int panther_plus_read_dimm_temp(struct i2c_client *client, int dimm, char *ret) { int rc; u32 val; int temp; /* * make sure POST is done, accessing DIMM temperature will fail anyway if * POST is not done. */ if (panther_plus_is_in_post(client)) { return -EBUSY; } mdelay(10); rc = panther_plus_read_register(client, dimm, &val); if (rc < 0) { return rc; } /* * DIMM temperature is encoded in 16b as the following: * b15-b12: TCRIT HIGH LOW SIGN * b11-b08: 128 64 32 16 * b07-b04: 8 4 2 1 * b03-b00: 0.5 0.25 0.125 0.0625 */ /* For now, only care about those 12 data bits and SIGN */ val &= 0x1FFF; if ((val & 0x1000)) { /* signed */ val = -((val - 1) ^ 0x1FFF); } /* * now val holds the value as a number of 1/16, times 1000 for lm-sensors */ temp = val * 1000 / 16; return sprintf(ret, "%d\n", temp); } static int panther_plus_read_gpio_inputs(struct i2c_client *client, char *ret) { u32 val; int rc; rc = panther_plus_read_gpio_inputs_value(client, &val); if (rc < 0) { return rc; } return sprintf(ret, "0x%x\n", val); } static int panther_plus_read_sms_kcs(struct i2c_client *client, char *ret) { int rc; u8 read_buf[255] = {0x0}; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_IPMI_SMS_KCS, NULL, 0, read_buf, sizeof(read_buf)); if (rc < 0) { return rc; } memcpy(ret, read_buf, read_buf[1]+2); return (read_buf[1]+2); } static int panther_plus_write_sms_kcs(struct i2c_client *client, const char *buf, u8 count) { int rc; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_IPMI_SMS_KCS, buf, count, NULL, 0); if (rc < 0) { return rc; } return count; } static int panther_plus_read_alert_status(struct i2c_client *client, char *ret) { int rc; u8 rbuf[5] = {0}; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_ALERT_STATUS, NULL, 0, rbuf, sizeof(rbuf)); if (rc < 0) { return rc; } memcpy(ret, rbuf, rbuf[1]+2); return (rbuf[1]+2); } static int panther_plus_read_alert_control(struct i2c_client *client, char *ret) { int rc; u8 rbuf[5] = {0}; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_ALERT_CONTROL, NULL, 0, rbuf, sizeof(rbuf)); if (rc < 0) { return rc; } memcpy(ret, rbuf, rbuf[1]+2); return (rbuf[1]+2); } static int panther_plus_read_discovery(struct i2c_client *client, char *ret, int which_attribute) { int rc; u8 datalen; #define DISCOVERY_DATA_SIZE 10 u8 rbuf[DISCOVERY_DATA_SIZE+2] = {0}; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_DISCOVERY, NULL, 0, rbuf, sizeof(rbuf)); if (rc < 0) { return rc; } datalen = rbuf[1]; if (datalen < DISCOVERY_DATA_SIZE) { return -EINVAL; } switch (which_attribute) { case PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER: return scnprintf(ret, PAGE_SIZE, "%u.%u\n", rbuf[2], rbuf[3]); case PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER: return scnprintf(ret, PAGE_SIZE, "%u.%u\n", rbuf[4], rbuf[5]); case PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID: return scnprintf(ret, PAGE_SIZE, "0x%02X%02X%02X\n", rbuf[8], rbuf[7], rbuf[6]); case PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID: return scnprintf(ret, PAGE_SIZE, "0x%02X\n", rbuf[9]); case PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID: return scnprintf(ret, PAGE_SIZE, "0x%02X%02X\n", rbuf[11], rbuf[10]); default: return -EINVAL; } return -EINVAL; } static int panther_plus_write_alert_control(struct i2c_client *client, const char *buf, u8 count) { int rc; rc = panther_plus_fbid_io(client, PANTHER_PLUS_FBID_ALERT_CONTROL, buf, count, NULL, 0); if (rc < 0) { return rc; } return count; } static ssize_t panther_plus_show(struct device *dev, struct device_attribute *attr, char *buf) { struct i2c_client *client = to_i2c_client(dev); struct panther_plus_data *data = i2c_get_clientdata(client); struct sensor_device_attribute_2 *sensor_attr = to_sensor_dev_attr_2(attr); int which = sensor_attr->index; int rc = -EIO; mutex_lock(&data->update_lock); switch (which) { case PANTHER_PLUS_SYSFS_CPU_TEMP: rc = panther_plus_read_cpu_temp(client, buf); break; case PANTHER_PLUS_SYSFS_DIMM_TEMP: rc = panther_plus_read_dimm_temp(client, sensor_attr->nr, buf); break; case PANTHER_PLUS_SYSFS_GPIO_INPUTS: rc = panther_plus_read_gpio_inputs(client, buf); break; case PANTHER_PLUS_SYSFS_SMS_KCS: rc = panther_plus_read_sms_kcs(client, buf); break; case PANTHER_PLUS_SYSFS_ALERT_STATUS: rc = panther_plus_read_alert_status(client, buf); break; case PANTHER_PLUS_SYSFS_ALERT_CONTROL: rc = panther_plus_read_alert_control(client, buf); break; case PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER: case PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER: case PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID: case PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID: case PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID: rc = panther_plus_read_discovery(client, buf, which); default: break; } /* * With the current i2c driver, the bus/kernel could hang if accessing the * FPGA too fast. Adding some delay here until we fix the i2c driver bug */ mdelay(10); mutex_unlock(&data->update_lock); return rc; } static ssize_t panther_plus_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); struct panther_plus_data *data = i2c_get_clientdata(client); struct sensor_device_attribute_2 *sensor_attr = to_sensor_dev_attr_2(attr); int which = sensor_attr->index; int rc = -EIO; mutex_lock(&data->update_lock); switch (which) { case PANTHER_PLUS_SYSFS_SMS_KCS: rc = panther_plus_write_sms_kcs(client, buf, count); break; case PANTHER_PLUS_SYSFS_ALERT_CONTROL: rc = panther_plus_write_alert_control(client, buf, count); break; default: break; } mdelay(10); mutex_unlock(&data->update_lock); return rc; } static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_CPU_TEMP); static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, panther_plus_show, NULL, PANTHER_PLUS_REG_SOC_DIMM0_A_TEMP, PANTHER_PLUS_SYSFS_DIMM_TEMP); static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, panther_plus_show, NULL, PANTHER_PLUS_REG_SOC_DIMM0_B_TEMP, PANTHER_PLUS_SYSFS_DIMM_TEMP); static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, panther_plus_show, NULL, PANTHER_PLUS_REG_SOC_DIMM1_A_TEMP, PANTHER_PLUS_SYSFS_DIMM_TEMP); static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, panther_plus_show, NULL, PANTHER_PLUS_REG_SOC_DIMM1_B_TEMP, PANTHER_PLUS_SYSFS_DIMM_TEMP); static SENSOR_DEVICE_ATTR_2(gpio_inputs, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_GPIO_INPUTS); static SENSOR_DEVICE_ATTR_2(sms_kcs, S_IWUSR | S_IRUGO, panther_plus_show, panther_plus_set, 0, PANTHER_PLUS_SYSFS_SMS_KCS); static SENSOR_DEVICE_ATTR_2(alert_status, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_ALERT_STATUS); static SENSOR_DEVICE_ATTR_2(alert_control, S_IWUSR | S_IRUGO, panther_plus_show, panther_plus_set, 0, PANTHER_PLUS_SYSFS_ALERT_CONTROL); static SENSOR_DEVICE_ATTR_2(spec_ver, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_DISCOVERY_SPEC_VER); static SENSOR_DEVICE_ATTR_2(hw_ver, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_DISCOVERY_HW_VER); static SENSOR_DEVICE_ATTR_2(manufacturer_id, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_DISCOVERY_MANUFACTURER_ID); static SENSOR_DEVICE_ATTR_2(device_id, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_DISCOVERY_DEVICE_ID); static SENSOR_DEVICE_ATTR_2(product_id, S_IRUGO, panther_plus_show, NULL, 0, PANTHER_PLUS_SYSFS_DISCOVERY_PRODUCT_ID); static struct attribute *panther_plus_attributes[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp2_input.dev_attr.attr, &sensor_dev_attr_temp3_input.dev_attr.attr, &sensor_dev_attr_temp4_input.dev_attr.attr, &sensor_dev_attr_temp5_input.dev_attr.attr, &sensor_dev_attr_gpio_inputs.dev_attr.attr, &sensor_dev_attr_sms_kcs.dev_attr.attr, &sensor_dev_attr_alert_status.dev_attr.attr, &sensor_dev_attr_alert_control.dev_attr.attr, &sensor_dev_attr_spec_ver.dev_attr.attr, &sensor_dev_attr_hw_ver.dev_attr.attr, &sensor_dev_attr_manufacturer_id.dev_attr.attr, &sensor_dev_attr_device_id.dev_attr.attr, &sensor_dev_attr_product_id.dev_attr.attr, NULL }; static const struct attribute_group panther_plus_group = { .attrs = panther_plus_attributes, }; /* Return 0 if detection is successful, -ENODEV otherwise */ static int panther_plus_detect(struct i2c_client *client, int kind, struct i2c_board_info *info) { /* * We don't currently do any detection of the Panther+, although * presumably we could try to query FBID 0xFF for HW ID. */ strlcpy(info->type, "panther_plus", I2C_NAME_SIZE); return 0; } static int panther_plus_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct panther_plus_data *data; int err; data = kzalloc(sizeof(struct panther_plus_data), GFP_KERNEL); if (!data) { err = -ENOMEM; goto exit; } i2c_set_clientdata(client, data); mutex_init(&data->update_lock); /* Register sysfs hooks */ if ((err = sysfs_create_group(&client->dev.kobj, &panther_plus_group))) goto exit_free; data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); goto exit_remove_files; } printk(KERN_INFO "Panther+ driver successfully loaded.\n"); return 0; exit_remove_files: sysfs_remove_group(&client->dev.kobj, &panther_plus_group); exit_free: kfree(data); exit: return err; } static int panther_plus_remove(struct i2c_client *client) { struct panther_plus_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&client->dev.kobj, &panther_plus_group); kfree(data); return 0; } static struct i2c_driver panther_plus_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "panther_plus", }, .probe = panther_plus_probe, .remove = panther_plus_remove, .id_table = panther_plus_id, .detect = panther_plus_detect, .address_data = &addr_data, }; static int __init sensors_panther_plus_init(void) { return i2c_add_driver(&panther_plus_driver); } static void __exit sensors_panther_plus_exit(void) { i2c_del_driver(&panther_plus_driver); } MODULE_AUTHOR("Tian Fang "); MODULE_DESCRIPTION("Panther+ Driver"); MODULE_LICENSE("GPL"); module_init(sensors_panther_plus_init); module_exit(sensors_panther_plus_exit);