diff options
Diffstat (limited to 'drivers/hwmon/fb_panther_plus.c')
-rw-r--r-- | drivers/hwmon/fb_panther_plus.c | 722 |
1 files changed, 722 insertions, 0 deletions
diff --git a/drivers/hwmon/fb_panther_plus.c b/drivers/hwmon/fb_panther_plus.c new file mode 100644 index 000000000000..1dde2ec1e029 --- /dev/null +++ b/drivers/hwmon/fb_panther_plus.c @@ -0,0 +1,722 @@ +/* + * 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 <linux/delay.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> + +#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 <gpio0-7> <gpio8-15> <gpio9-23> + */ + 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 <reg_idx> 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 <tfang@fb.com>"); +MODULE_DESCRIPTION("Panther+ Driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_panther_plus_init); +module_exit(sensors_panther_plus_exit); |