From nobody Wed Dec 17 19:24:29 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9DC56C7EE21 for ; Tue, 18 Apr 2023 15:32:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231148AbjDRPcO (ORCPT ); Tue, 18 Apr 2023 11:32:14 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47938 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232304AbjDRPcK (ORCPT ); Tue, 18 Apr 2023 11:32:10 -0400 Received: from mx0a-002e3701.pphosted.com (mx0a-002e3701.pphosted.com [148.163.147.86]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8444E10D8; Tue, 18 Apr 2023 08:32:07 -0700 (PDT) Received: from pps.filterd (m0148663.ppops.net [127.0.0.1]) by mx0a-002e3701.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 33IEutOL000767; Tue, 18 Apr 2023 15:31:38 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=hpe.com; h=from : to : subject : date : message-id : in-reply-to : references; s=pps0720; bh=Gkats5dbf8WnZT7mgL+e/Kl1YGcTI9hrVc/iSuuyick=; b=VdC1e+3co4/H4atM6L5OK8B5z/ab0tCVn2gpy/jHNe/7BsHRIPBCZSxrbeFrIYoQVf+Y 1Q5ZVRp1LfWDm9NXI+pTBa+l6PCsK5iqBzA0vvjDHmLcjWySmPqQuUaZ/XV9E2V9W04X FKHQXJAKFn+P7TVYcy88C7MhMoodUr+G5cRJAqeq4qr5xu161Argsv2wc/GU1P3ikgK4 fxijPccXLiStcsBEh853kLlL6f/Pr6DS6kcA93Jluw92jPMOfR903mDQFWfClkwGw/G7 Gzv2e/oacO7F5ov32eYztKnEAMCHzhP/qQ2TMiYf/uZJCVt6cgaRCJ4r2+K0tQMWsnrU fQ== Received: from p1lg14881.it.hpe.com (p1lg14881.it.hpe.com [16.230.97.202]) by mx0a-002e3701.pphosted.com (PPS) with ESMTPS id 3q1wdw0b8x-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 18 Apr 2023 15:31:37 +0000 Received: from p1lg14886.dc01.its.hpecorp.net (unknown [10.119.18.237]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by p1lg14881.it.hpe.com (Postfix) with ESMTPS id 2FD4E806B40; Tue, 18 Apr 2023 15:31:37 +0000 (UTC) Received: from hpe.com (unknown [16.231.227.36]) by p1lg14886.dc01.its.hpecorp.net (Postfix) with ESMTP id 7671781532D; Tue, 18 Apr 2023 15:31:36 +0000 (UTC) From: nick.hawkins@hpe.com To: verdun@hpe.com, nick.hawkins@hpe.com, linus.walleij@linaro.org, brgl@bgdev.pl, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org, jdelvare@suse.com, linux@roeck-us.net, linux@armlinux.org.uk, linux-gpio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v1 3/9] hwmon: (gxp-psu) Add driver to read HPE PSUs Date: Tue, 18 Apr 2023 10:28:18 -0500 Message-Id: <20230418152824.110823-4-nick.hawkins@hpe.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20230418152824.110823-1-nick.hawkins@hpe.com> References: <20230418152824.110823-1-nick.hawkins@hpe.com> X-Proofpoint-ORIG-GUID: Fn671cNE7gO5dLU5ieyZxcCyaATF0vl- X-Proofpoint-GUID: Fn671cNE7gO5dLU5ieyZxcCyaATF0vl- X-HPE-SCL: -1 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-04-18_11,2023-04-18_01,2023-02-09_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 impostorscore=0 mlxscore=0 lowpriorityscore=0 priorityscore=1501 clxscore=1015 suspectscore=0 mlxlogscore=999 phishscore=0 bulkscore=0 malwarescore=0 spamscore=0 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2303200000 definitions=main-2304180132 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" From: Nick Hawkins The GXP SoC can support up to 8 power supplies that it can communicate with via i2c. The GXP PSU driver will provide a method for the GPIO driver with info it reads to be available to the host. Signed-off-by: Nick Hawkins --- drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gxp-psu.c | 773 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 784 insertions(+) create mode 100644 drivers/hwmon/gxp-psu.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5b3b76477b0e..3b56690ea089 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -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. =20 +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 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 88712b5031c8..6c84cd52d0d3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_SENSORS_GL520SM) +=3D gl520sm.o obj-$(CONFIG_SENSORS_GSC) +=3D gsc-hwmon.o obj-$(CONFIG_SENSORS_GPIO_FAN) +=3D gpio-fan.o obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) +=3D gxp-fan-ctrl.o +obj-$(CONFIG_SENSORS_GXP_PSU) +=3D gxp-psu.o obj-$(CONFIG_SENSORS_HIH6130) +=3D hih6130.o obj-$(CONFIG_SENSORS_ULTRA45) +=3D ultra45_env.o obj-$(CONFIG_SENSORS_I5500) +=3D i5500_temp.o diff --git a/drivers/hwmon/gxp-psu.c b/drivers/hwmon/gxp-psu.c new file mode 100644 index 000000000000..e4217200c34b --- /dev/null +++ b/drivers/hwmon/gxp-psu.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2023 Hewlett-Packard Enterpise Development Company, L.P. = */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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] =3D { 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 =3D (unsigned long)readb(pl_psu); + + for_each_set_bit(i, &temp, 8) { + if (i =3D=3D id) + psus[id]->present =3D true; + } + + temp =3D ~temp; + for_each_set_bit(i, &temp, 8) { + if (i =3D=3D id) + psus[id]->present =3D false; + } +} + +static unsigned char cal_checksum(unsigned char *buf, unsigned long size) +{ + unsigned char sum =3D 0; + + while (size > 0) { + sum +=3D (*(buf++)); + size--; + } + return ((~sum) + 1); +} + +static unsigned char valid_checksum(unsigned char *buf, unsigned long size) +{ + unsigned char sum =3D 0; + + while (size > 0) { + sum +=3D (*(buf++)); + size--; + } + return sum; +} + +static int psu_read_fru(struct gxp_psu_drvdata *drvdata, + u8 offset, u8 length, u8 *value) +{ + struct i2c_client *client =3D drvdata->client; + unsigned char buf_tx[4] =3D {(client->addr << 1), READ_FRU_CMD, offset, l= ength}; + unsigned char tx[4] =3D {0}; + unsigned char chksum =3D cal_checksum(buf_tx, 4); + int ret =3D 0; + struct i2c_msg msgs[2] =3D {0}; + + update_presence(drvdata->id); + + value[0] =3D '\0'; + + if (!drvdata->present) + return -EOPNOTSUPP; + + tx[0] =3D READ_FRU_CMD; + tx[1] =3D offset; + tx[2] =3D length; + tx[3] =3D chksum; + msgs[0].addr =3D client->addr; + msgs[0].flags =3D 0; + msgs[0].buf =3D tx; + msgs[0].len =3D 4; + msgs[1].addr =3D client->addr; + msgs[1].flags =3D I2C_M_RD; + msgs[1].buf =3D value; + msgs[1].len =3D length; + + mutex_lock(&drvdata->update_lock); + ret =3D 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 =3D drvdata->client; + unsigned char buf_tx[3] =3D {(client->addr << 1), READ_REG_CMD, reg}; + unsigned char buf_rx[3] =3D {0}; + unsigned char tx[3] =3D {0}; + unsigned char rx[3] =3D {0}; + unsigned char chksum =3D cal_checksum(buf_tx, 3); + struct i2c_msg msgs[2] =3D {0}; + int ret =3D 0; + + tx[0] =3D 0; + tx[1] =3D reg; + tx[2] =3D chksum; + + msgs[0].addr =3D client->addr; + msgs[0].flags =3D 0; + msgs[0].buf =3D tx; + msgs[0].len =3D 3; + msgs[1].addr =3D client->addr; + msgs[1].flags =3D I2C_M_RD; + msgs[1].buf =3D rx; + msgs[1].len =3D 3; + mutex_lock(&drvdata->update_lock); + ret =3D 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] =3D rx[0]; + buf_rx[1] =3D rx[1]; + buf_rx[2] =3D rx[2]; + if (valid_checksum(buf_rx, 3) !=3D 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 =3D rx[0] + (rx[1] << 8); + + return ret; +} + +static int gxppsu_update_client(struct device *dev, u8 reg) +{ + struct gxp_psu_drvdata *drvdata =3D dev_get_drvdata(dev); + int ret =3D 0; + + update_presence(drvdata->id); + + if (!drvdata->present) + return -EOPNOTSUPP; + + switch (reg) { + case REG_IN_PWR: + ret =3D psu_read_reg_word(drvdata, REG_IN_PWR, + &drvdata->input_power); + break; + case REG_IN_VOL: + ret =3D psu_read_reg_word(drvdata, REG_IN_VOL, + &drvdata->input_voltage); + break; + case REG_IN_CUR: + ret =3D psu_read_reg_word(drvdata, REG_IN_CUR, + &drvdata->input_current); + break; + case REG_OUT_PWR: + ret =3D psu_read_reg_word(drvdata, REG_OUT_PWR, + &drvdata->output_power); + break; + case REG_OUT_VOL: + ret =3D psu_read_reg_word(drvdata, REG_OUT_VOL, + &drvdata->output_voltage); + break; + case REG_OUT_CUR: + ret =3D psu_read_reg_word(drvdata, REG_OUT_CUR, + &drvdata->output_current); + break; + case REG_INLET_TEMP: + ret =3D psu_read_reg_word(drvdata, REG_INLET_TEMP, + &drvdata->inlet_temp); + break; + case REG_FAN_SPEED: + ret =3D 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 chann= el, + long *val) +{ + struct gxp_psu_drvdata *drvdata =3D dev_get_drvdata(dev); + int ret =3D 0; + u8 reg; + + if (channel =3D=3D INPUT_CHAN) + reg =3D REG_IN_PWR; + else + reg =3D REG_OUT_PWR; + + ret =3D gxppsu_update_client(dev, reg); + if (ret < 0) + return ret; + + if (channel =3D=3D INPUT_CHAN) + *val =3D drvdata->input_power; + else + *val =3D 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 =3D dev_get_drvdata(dev); + int ret =3D 0; + u8 reg; + + if (channel =3D=3D INPUT_CHAN) + reg =3D REG_IN_VOL; + else + reg =3D REG_OUT_VOL; + + ret =3D gxppsu_update_client(dev, reg); + if (ret < 0) + return ret; + + if (channel =3D=3D INPUT_CHAN) + *val =3D drvdata->input_voltage; + else + *val =3D drvdata->output_voltage; + + return 0; +} + +static int gxp_psu_get_curr_input(struct device *dev, u32 attr, int channe= l, + long *val) +{ + struct gxp_psu_drvdata *drvdata =3D dev_get_drvdata(dev); + int ret =3D 0; + u8 reg; + + if (channel =3D=3D INPUT_CHAN) + reg =3D REG_IN_CUR; + else + reg =3D REG_OUT_CUR; + + ret =3D gxppsu_update_client(dev, reg); + + if (ret < 0) + return ret; + + if (channel =3D=3D INPUT_CHAN) + *val =3D drvdata->input_current; + else + *val =3D drvdata->output_current; + + return 0; +} + +static int gxp_psu_get_temp_input(struct device *dev, u32 attr, int channe= l, + long *val) +{ + struct gxp_psu_drvdata *drvdata =3D dev_get_drvdata(dev); + int ret =3D 0; + u8 reg =3D REG_INLET_TEMP; + + ret =3D gxppsu_update_client(dev, reg); + if (ret < 0) + return ret; + + *val =3D 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 =3D dev_get_drvdata(dev); + int ret =3D 0; + u8 reg =3D REG_FAN_SPEED; + + ret =3D gxppsu_update_client(dev, reg); + if (ret < 0) + return ret; + + *val =3D drvdata->fan_speed; + + return 0; +} + +void swapbytes(void *input, size_t len) +{ + unsigned int i; + unsigned char *in =3D (unsigned char *)input, tmp; + + for (i =3D 0; i < len / 2; i++) { + tmp =3D *(in + i); + *(in + i) =3D *(in + len - i - 1); + *(in + len - i - 1) =3D tmp; + } +} + +static const struct hwmon_channel_info *gxp_psu_info[] =3D { + 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_typ= es type, + u32 attr, int channel) +{ + umode_t mode =3D 0; + + switch (type) { + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_label: + mode =3D 0444; + break; + default: + break; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + mode =3D 0444; + break; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + mode =3D 0444; + break; + default: + break; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_label: + mode =3D 0444; + break; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + mode =3D 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 =3D channel ? L_OUT_POWER : L_IN_POWER; + return 0; + default: + break; + } + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *str =3D channel ? L_OUT_IN : L_IN_IN; + return 0; + default: + break; + } + case hwmon_fan: + switch (attr) { + case hwmon_fan_label: + *str =3D L_FAN; + return 0; + default: + break; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_label: + *str =3D channel ? L_OUT_CURR : L_IN_CURR; + return 0; + default: + break; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str =3D L_TEMP; + return 0; + default: + break; + } + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops gxp_psu_ops =3D { + .is_visible =3D gxp_psu_is_visible, + .read =3D gxp_psu_read, + .read_string =3D gxp_psu_read_string, +}; + +static const struct hwmon_chip_info gxp_psu_chip_info =3D { + .ops =3D &gxp_psu_ops, + .info =3D gxp_psu_info, +}; + +#ifdef CONFIG_DEBUG_FS + +static int serial_number_show(struct seq_file *seqf, void *unused) +{ + struct gxp_psu_drvdata *drvdata =3D seqf->private; + int ret =3D 0; + + ret =3D 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 =3D seqf->private; + int ret =3D 0; + + ret =3D 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 =3D seqf->private; + int ret =3D 0; + + ret =3D 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 =3D seqf->private; + int ret =3D 0; + + ret =3D 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 =3D 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 =3D debugfs_create_dir(name, NULL); + + debugfs_create_file("serial_number", 0444, drvdata->debugfs, drvdata, &se= rial_number_fops); + + debugfs_create_file("manufacturer", 0444, drvdata->debugfs, drvdata, &man= ufacturer_fops); + + debugfs_create_file("product_name", 0444, drvdata->debugfs, drvdata, &pro= duct_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_SMB= US_EMUL)) + return -EIO; + + drvdata =3D devm_kzalloc(&client->dev, sizeof(struct gxp_psu_drvdata), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + if (!pl_psu) { + np =3D of_parse_phandle(client->dev.of_node, "hpe,sysreg", 0); + if (!np) + return -ENODEV; + + pl_psu =3D 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 =3D client; + i2c_set_clientdata(client, drvdata); + + mutex_init(&drvdata->update_lock); + drvdata->hwmon_dev =3D NULL; + hwmon_dev =3D 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 =3D hwmon_dev; + + drvdata->id =3D psucount; + + psus[psucount] =3D drvdata; + + update_presence(drvdata->id); + + psucount++; + + gxp_psu_debugfs_init(drvdata); + + return 0; +} + +static const struct of_device_id gxp_psu_of_match[] =3D { + { .compatible =3D "hpe,gxp-psu" }, + {}, +}; +MODULE_DEVICE_TABLE(of, gxp_psu_of_match); + +static struct i2c_driver gxp_psu_driver =3D { + .class =3D I2C_CLASS_HWMON, + .driver =3D { + .name =3D "gxp-psu", + .of_match_table =3D gxp_psu_of_match, + }, + .probe_new =3D gxp_psu_probe, +}; +module_i2c_driver(gxp_psu_driver); + +MODULE_AUTHOR("Nick Hawkins "); +MODULE_DESCRIPTION("HPE GXP PSU driver"); +MODULE_LICENSE("GPL"); --=20 2.17.1