@@ -723,6 +723,16 @@ config SENSORS_GXP_FAN_CTRL
The GXP controls fan function via the CPLD through the use of PWM
registers. This driver reports status and pwm setting of the fans.
+config SENSORS_GXP_PSU
+ tristate "HPE GXP PSU driver"
+ depends on ARCH_HPE_GXP || COMPILE_TEST
+ depends on I2C
+ help
+ If you say yes you will get support for GXP psu driver support. The GXP
+ gets PSU presence and state information from the CPLD. The GXP gets PSU
+ data via i2c. It provides a method for other drivers to call into get
+ PSU presence information.
+
config SENSORS_HIH6130
tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor"
depends on I2C
@@ -84,6 +84,7 @@ obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o
+obj-$(CONFIG_SENSORS_GXP_PSU) += gxp-psu.o
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
obj-$(CONFIG_SENSORS_I5500) += i5500_temp.o
new file mode 100644
@@ -0,0 +1,773 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2023 Hewlett-Packard Enterpise Development Company, L.P. */
+
+#include <linux/err.h>
+#include <linux/debugfs.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+
+#define READ_REG_CMD 0x00
+#define READ_FRU_CMD 0x22
+#define REG_IN_VOL 0x10
+#define REG_IN_CUR 0x11
+#define REG_IN_PWR 0x12
+#define REG_OUT_VOL 0x20
+#define REG_OUT_CUR 0x21
+#define REG_OUT_PWR 0x22
+#define REG_FAN_SPEED 0x40
+#define REG_INLET_TEMP 0x42
+#define MAX_PSU 0x08
+#define INPUT_CHAN 0
+
+#define L_IN_POWER "pin1"
+#define L_OUT_POWER "pout1"
+#define L_IN_IN "vin"
+#define L_OUT_IN "vout1"
+#define L_FAN "fan1"
+#define L_IN_CURR "iin"
+#define L_OUT_CURR "iout1"
+#define L_TEMP "temp1"
+
+static void __iomem *pl_psu;
+
+struct gxp_psu_drvdata {
+ struct i2c_client *client;
+ u16 input_power;
+ u16 input_voltage;
+ u16 input_current;
+ u16 output_power;
+ u16 output_voltage;
+ u16 output_current;
+ s16 inlet_temp;
+ u16 fan_speed;
+ u8 id;
+ struct dentry *debugfs;
+ u8 spare_part[10];
+ u8 product_name[26];
+ u8 serial_number[14];
+ u8 product_manufacturer[3];
+ bool present; /* psu can be physically removed */
+ struct mutex update_lock;
+ struct device *hwmon_dev;
+};
+
+static u8 psucount;
+struct gxp_psu_drvdata *psus[MAX_PSU] = { NULL };
+
+u8 get_psu_inst(void)
+{
+ if (!pl_psu)
+ return 0;
+
+ return readb(pl_psu);
+}
+EXPORT_SYMBOL(get_psu_inst);
+
+u8 get_psu_ac(void)
+{
+ if (!pl_psu)
+ return 0;
+
+ return readb(pl_psu + 0x02);
+}
+EXPORT_SYMBOL(get_psu_ac);
+
+u8 get_psu_dc(void)
+{
+ if (!pl_psu)
+ return 0;
+
+ return readb(pl_psu + 0x03);
+}
+EXPORT_SYMBOL(get_psu_dc);
+
+void update_presence(u8 id)
+{
+ unsigned int i;
+ unsigned long temp = (unsigned long)readb(pl_psu);
+
+ for_each_set_bit(i, &temp, 8) {
+ if (i == id)
+ psus[id]->present = true;
+ }
+
+ temp = ~temp;
+ for_each_set_bit(i, &temp, 8) {
+ if (i == id)
+ psus[id]->present = false;
+ }
+}
+
+static unsigned char cal_checksum(unsigned char *buf, unsigned long size)
+{
+ unsigned char sum = 0;
+
+ while (size > 0) {
+ sum += (*(buf++));
+ size--;
+ }
+ return ((~sum) + 1);
+}
+
+static unsigned char valid_checksum(unsigned char *buf, unsigned long size)
+{
+ unsigned char sum = 0;
+
+ while (size > 0) {
+ sum += (*(buf++));
+ size--;
+ }
+ return sum;
+}
+
+static int psu_read_fru(struct gxp_psu_drvdata *drvdata,
+ u8 offset, u8 length, u8 *value)
+{
+ struct i2c_client *client = drvdata->client;
+ unsigned char buf_tx[4] = {(client->addr << 1), READ_FRU_CMD, offset, length};
+ unsigned char tx[4] = {0};
+ unsigned char chksum = cal_checksum(buf_tx, 4);
+ int ret = 0;
+ struct i2c_msg msgs[2] = {0};
+
+ update_presence(drvdata->id);
+
+ value[0] = '\0';
+
+ if (!drvdata->present)
+ return -EOPNOTSUPP;
+
+ tx[0] = READ_FRU_CMD;
+ tx[1] = offset;
+ tx[2] = length;
+ tx[3] = chksum;
+ msgs[0].addr = client->addr;
+ msgs[0].flags = 0;
+ msgs[0].buf = tx;
+ msgs[0].len = 4;
+ msgs[1].addr = client->addr;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].buf = value;
+ msgs[1].len = length;
+
+ mutex_lock(&drvdata->update_lock);
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ mutex_unlock(&drvdata->update_lock);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "gxppsu_i2c_tx_fail addr:0x%x offest:0x%x length:0x%x chk:0x%x ret:0x%x\n",
+ client->addr, offset, length, chksum, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int psu_read_reg_word(struct gxp_psu_drvdata *drvdata,
+ u8 reg, u16 *value)
+{
+ struct i2c_client *client = drvdata->client;
+ unsigned char buf_tx[3] = {(client->addr << 1), READ_REG_CMD, reg};
+ unsigned char buf_rx[3] = {0};
+ unsigned char tx[3] = {0};
+ unsigned char rx[3] = {0};
+ unsigned char chksum = cal_checksum(buf_tx, 3);
+ struct i2c_msg msgs[2] = {0};
+ int ret = 0;
+
+ tx[0] = 0;
+ tx[1] = reg;
+ tx[2] = chksum;
+
+ msgs[0].addr = client->addr;
+ msgs[0].flags = 0;
+ msgs[0].buf = tx;
+ msgs[0].len = 3;
+ msgs[1].addr = client->addr;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].buf = rx;
+ msgs[1].len = 3;
+ mutex_lock(&drvdata->update_lock);
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ mutex_unlock(&drvdata->update_lock);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "gxppsu_i2c_tx_fail addr:0x%x reg:0x%x chk:0x%x ret:0x%x\n",
+ client->addr, reg, chksum, ret);
+ return ret;
+ }
+
+ buf_rx[0] = rx[0];
+ buf_rx[1] = rx[1];
+ buf_rx[2] = rx[2];
+ if (valid_checksum(buf_rx, 3) != 0) {
+ dev_err(&client->dev,
+ "gxppsu_checksum_fail addr:0x%x reg:0x%x, data:%x %x %x\n",
+ client->addr, reg, rx[0], rx[1], rx[2]);
+ return -EAGAIN;
+ }
+
+ *value = rx[0] + (rx[1] << 8);
+
+ return ret;
+}
+
+static int gxppsu_update_client(struct device *dev, u8 reg)
+{
+ struct gxp_psu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret = 0;
+
+ update_presence(drvdata->id);
+
+ if (!drvdata->present)
+ return -EOPNOTSUPP;
+
+ switch (reg) {
+ case REG_IN_PWR:
+ ret = psu_read_reg_word(drvdata, REG_IN_PWR,
+ &drvdata->input_power);
+ break;
+ case REG_IN_VOL:
+ ret = psu_read_reg_word(drvdata, REG_IN_VOL,
+ &drvdata->input_voltage);
+ break;
+ case REG_IN_CUR:
+ ret = psu_read_reg_word(drvdata, REG_IN_CUR,
+ &drvdata->input_current);
+ break;
+ case REG_OUT_PWR:
+ ret = psu_read_reg_word(drvdata, REG_OUT_PWR,
+ &drvdata->output_power);
+ break;
+ case REG_OUT_VOL:
+ ret = psu_read_reg_word(drvdata, REG_OUT_VOL,
+ &drvdata->output_voltage);
+ break;
+ case REG_OUT_CUR:
+ ret = psu_read_reg_word(drvdata, REG_OUT_CUR,
+ &drvdata->output_current);
+ break;
+ case REG_INLET_TEMP:
+ ret = psu_read_reg_word(drvdata, REG_INLET_TEMP,
+ &drvdata->inlet_temp);
+ break;
+ case REG_FAN_SPEED:
+ ret = psu_read_reg_word(drvdata, REG_FAN_SPEED,
+ &drvdata->fan_speed);
+ break;
+ default:
+ dev_err(&drvdata->client->dev, "gxppsu_error_reg 0x%x\n", reg);
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int gxp_psu_get_power_input(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct gxp_psu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret = 0;
+ u8 reg;
+
+ if (channel == INPUT_CHAN)
+ reg = REG_IN_PWR;
+ else
+ reg = REG_OUT_PWR;
+
+ ret = gxppsu_update_client(dev, reg);
+ if (ret < 0)
+ return ret;
+
+ if (channel == INPUT_CHAN)
+ *val = drvdata->input_power;
+ else
+ *val = drvdata->output_power;
+
+ return 0;
+}
+
+static int gxp_psu_get_in_input(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct gxp_psu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret = 0;
+ u8 reg;
+
+ if (channel == INPUT_CHAN)
+ reg = REG_IN_VOL;
+ else
+ reg = REG_OUT_VOL;
+
+ ret = gxppsu_update_client(dev, reg);
+ if (ret < 0)
+ return ret;
+
+ if (channel == INPUT_CHAN)
+ *val = drvdata->input_voltage;
+ else
+ *val = drvdata->output_voltage;
+
+ return 0;
+}
+
+static int gxp_psu_get_curr_input(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct gxp_psu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret = 0;
+ u8 reg;
+
+ if (channel == INPUT_CHAN)
+ reg = REG_IN_CUR;
+ else
+ reg = REG_OUT_CUR;
+
+ ret = gxppsu_update_client(dev, reg);
+
+ if (ret < 0)
+ return ret;
+
+ if (channel == INPUT_CHAN)
+ *val = drvdata->input_current;
+ else
+ *val = drvdata->output_current;
+
+ return 0;
+}
+
+static int gxp_psu_get_temp_input(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct gxp_psu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret = 0;
+ u8 reg = REG_INLET_TEMP;
+
+ ret = gxppsu_update_client(dev, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = drvdata->inlet_temp;
+
+ return 0;
+};
+
+static int gxp_psu_get_fan_input(struct device *dev, u32 attr, int channel,
+ long *val)
+{
+ struct gxp_psu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret = 0;
+ u8 reg = REG_FAN_SPEED;
+
+ ret = gxppsu_update_client(dev, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = drvdata->fan_speed;
+
+ return 0;
+}
+
+void swapbytes(void *input, size_t len)
+{
+ unsigned int i;
+ unsigned char *in = (unsigned char *)input, tmp;
+
+ for (i = 0; i < len / 2; i++) {
+ tmp = *(in + i);
+ *(in + i) = *(in + len - i - 1);
+ *(in + len - i - 1) = tmp;
+ }
+}
+
+static const struct hwmon_channel_info *gxp_psu_info[] = {
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_LABEL,
+ HWMON_P_INPUT | HWMON_P_LABEL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LABEL,
+ HWMON_C_INPUT | HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static umode_t gxp_psu_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ umode_t mode = 0;
+
+ switch (type) {
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_input:
+ case hwmon_power_label:
+ mode = 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_label:
+ mode = 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ case hwmon_fan_label:
+ mode = 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_curr_label:
+ mode = 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ case hwmon_temp_label:
+ mode = 0444;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return mode;
+}
+
+static int gxp_psu_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (type) {
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_input:
+ return gxp_psu_get_power_input(dev, attr, channel,
+ val);
+ default:
+ break;
+ }
+ break;
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ return gxp_psu_get_in_input(dev, attr, channel,
+ val);
+ default:
+ break;
+ }
+ break;
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ return gxp_psu_get_fan_input(dev, attr, channel,
+ val);
+ default:
+ break;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ return gxp_psu_get_curr_input(dev, attr, channel,
+ val);
+ default:
+ break;
+ }
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ return gxp_psu_get_temp_input(dev, attr, channel,
+ val);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static int gxp_psu_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_power:
+ switch (attr) {
+ case hwmon_power_label:
+ *str = channel ? L_OUT_POWER : L_IN_POWER;
+ return 0;
+ default:
+ break;
+ }
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_label:
+ *str = channel ? L_OUT_IN : L_IN_IN;
+ return 0;
+ default:
+ break;
+ }
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_label:
+ *str = L_FAN;
+ return 0;
+ default:
+ break;
+ }
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_label:
+ *str = channel ? L_OUT_CURR : L_IN_CURR;
+ return 0;
+ default:
+ break;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ *str = L_TEMP;
+ return 0;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops gxp_psu_ops = {
+ .is_visible = gxp_psu_is_visible,
+ .read = gxp_psu_read,
+ .read_string = gxp_psu_read_string,
+};
+
+static const struct hwmon_chip_info gxp_psu_chip_info = {
+ .ops = &gxp_psu_ops,
+ .info = gxp_psu_info,
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+static int serial_number_show(struct seq_file *seqf, void *unused)
+{
+ struct gxp_psu_drvdata *drvdata = seqf->private;
+ int ret = 0;
+
+ ret = psu_read_fru(drvdata, 91, 14, &drvdata->serial_number[0]);
+ if (ret < 0) {
+ dev_err(&drvdata->client->dev, "Unknown product serial number %d", ret);
+ seq_puts(seqf, "unknown\n");
+ return 0;
+ }
+
+ seq_printf(seqf, "%s\n", drvdata->serial_number);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(serial_number);
+
+static int manufacturer_show(struct seq_file *seqf, void *unused)
+{
+ struct gxp_psu_drvdata *drvdata = seqf->private;
+ int ret = 0;
+
+ ret = psu_read_fru(drvdata, 197, 3, &drvdata->product_manufacturer[0]);
+ if (ret < 0) {
+ dev_err(&drvdata->client->dev, "Unknown product manufacturer %d", ret);
+ seq_puts(seqf, "unknown\n");
+ return 0;
+ }
+
+ swapbytes(&drvdata->product_manufacturer[0], 3);
+ seq_printf(seqf, "%s\n", drvdata->product_manufacturer);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(manufacturer);
+
+static int product_name_show(struct seq_file *seqf, void *unused)
+{
+ struct gxp_psu_drvdata *drvdata = seqf->private;
+ int ret = 0;
+
+ ret = psu_read_fru(drvdata, 50, 26, &drvdata->product_name[0]);
+ if (ret < 0) {
+ dev_err(&drvdata->client->dev, "Unknown product name %d", ret);
+ seq_puts(seqf, "unknown\n");
+ return 0;
+ }
+
+ seq_printf(seqf, "%s\n", drvdata->product_name);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(product_name);
+
+static int spare_show(struct seq_file *seqf, void *unused)
+{
+ struct gxp_psu_drvdata *drvdata = seqf->private;
+ int ret = 0;
+
+ ret = psu_read_fru(drvdata, 18, 10, &drvdata->spare_part[0]);
+ if (ret < 0) {
+ dev_err(&drvdata->client->dev, "Unknown product spare %d", ret);
+ seq_puts(seqf, "unknown\n");
+ return 0;
+ }
+
+ seq_printf(seqf, "%s\n", drvdata->spare_part);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(spare);
+
+static int present_show(struct seq_file *seqf, void *unused)
+{
+ struct gxp_psu_drvdata *drvdata = seqf->private;
+
+ update_presence(drvdata->id);
+
+ if (drvdata->present)
+ seq_puts(seqf, "yes\n");
+ else
+ seq_puts(seqf, "no\n");
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(present);
+
+static void gxp_psu_debugfs_init(struct gxp_psu_drvdata *drvdata)
+{
+ char name[32];
+
+ scnprintf(name, sizeof(name), "%s_%s-%d", dev_name(drvdata->hwmon_dev),
+ drvdata->client->name, drvdata->client->addr);
+
+ drvdata->debugfs = debugfs_create_dir(name, NULL);
+
+ debugfs_create_file("serial_number", 0444, drvdata->debugfs, drvdata, &serial_number_fops);
+
+ debugfs_create_file("manufacturer", 0444, drvdata->debugfs, drvdata, &manufacturer_fops);
+
+ debugfs_create_file("product_name", 0444, drvdata->debugfs, drvdata, &product_name_fops);
+
+ debugfs_create_file("spare", 0444, drvdata->debugfs, drvdata, &spare_fops);
+
+ debugfs_create_file("present", 0444, drvdata->debugfs, drvdata, &present_fops);
+
+ if (!debugfs_initialized())
+ dev_err(&drvdata->client->dev, "Debug FS not Init");
+}
+
+#else
+
+static void gxp_psu_debugfs_init(struct gxp_psu_drvdata *drvdata)
+{
+}
+
+#endif
+
+static int gxp_psu_probe(struct i2c_client *client)
+{
+ struct gxp_psu_drvdata *drvdata;
+ struct device *hwmon_dev;
+ struct device_node *np;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL))
+ return -EIO;
+
+ drvdata = devm_kzalloc(&client->dev, sizeof(struct gxp_psu_drvdata),
+ GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ if (!pl_psu) {
+ np = of_parse_phandle(client->dev.of_node, "hpe,sysreg", 0);
+ if (!np)
+ return -ENODEV;
+
+ pl_psu = of_iomap(np, 0);
+ if (IS_ERR(pl_psu))
+ return dev_err_probe(&client->dev, IS_ERR(pl_psu),
+ "failed to map pl_psu");
+ }
+
+ drvdata->client = client;
+ i2c_set_clientdata(client, drvdata);
+
+ mutex_init(&drvdata->update_lock);
+ drvdata->hwmon_dev = NULL;
+ hwmon_dev = devm_hwmon_device_register_with_info(&client->dev,
+ "hpe_gxp_psu",
+ drvdata,
+ &gxp_psu_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ drvdata->hwmon_dev = hwmon_dev;
+
+ drvdata->id = psucount;
+
+ psus[psucount] = drvdata;
+
+ update_presence(drvdata->id);
+
+ psucount++;
+
+ gxp_psu_debugfs_init(drvdata);
+
+ return 0;
+}
+
+static const struct of_device_id gxp_psu_of_match[] = {
+ { .compatible = "hpe,gxp-psu" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gxp_psu_of_match);
+
+static struct i2c_driver gxp_psu_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "gxp-psu",
+ .of_match_table = gxp_psu_of_match,
+ },
+ .probe_new = gxp_psu_probe,
+};
+module_i2c_driver(gxp_psu_driver);
+
+MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>");
+MODULE_DESCRIPTION("HPE GXP PSU driver");
+MODULE_LICENSE("GPL");