From nobody Thu Apr 2 19:02:13 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F25E439A7F6; Thu, 26 Mar 2026 19:22:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552967; cv=pass; b=iizARGYNJdGNMQzWP+oEc1rdLNQTFVFjQW2gMFDgfzJbp3ffaOw/gIuQBpNeHp/HGIsIZYj5A1pLgRCgyFMifacbMYC4tDom8sdM4TAYciBhSkXXVOQmEz5k5C/+iQvvLA+4WHlIMrMlwoJVUxNU9fW+jfX83lH6QxMx4eqe1ns= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552967; c=relaxed/simple; bh=XL88iy7BHhBsEEyA56+hg3U22V/liVvxCQTakKsbOFs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hjjIuyUZK49aUDR8JsjGtRYYDes+AvtMPVzCBeEMmq00J8qBGuf1MlE6rzxR5QZIWRUpiknp69ou93n6PmljVFMsJL20hDRr+fqbtjekGAHUqpbg1jCHI4OPwTDo/yB3ANl47lyIvuNiUqllQpGEHsbOG7SOU8bZKbtrsPVWu5Y= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=IXK4e328; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="IXK4e328" ARC-Seal: i=1; a=rsa-sha256; t=1774552950; cv=none; d=zohomail.com; s=zohoarc; b=CQBucm9LvFzAZm6V04gJO4fJChQvUP593rvNblDNQqwkcAWI8rUejV66bW3DZKN+/Iam48E/H1atJo3ioRcfCZO+rWWuTs9HSgau9/nA1xRFrByv7x3y/I74zgd5pN8QaykUbkkVYSWwuV3ZZ/6baVGLsdobQ4VFCoJjgsHcRv4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1774552950; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=5HBd70CTqm2cD5uQkmhrerSMpwmJp9KsG3MTUzotqFI=; b=hiv1ZKCBp5u2H9nllWIi6oh8XWbK+qbxAq8VzrcYvCAHV+7at4jpL7BtPyw8iU7vrDYIZwrVCUN9OixzC2y39Y0OMTKi6DepvC2zwa+sak4zbkbhzZqalx5BXj6BPV5C72Zw/DaKE7rSx3nCIq+RxdflGyvA3dGYgu6WZ7MC4OA= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1774552950; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:References:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=5HBd70CTqm2cD5uQkmhrerSMpwmJp9KsG3MTUzotqFI=; b=IXK4e328CwWYVIfTTkROC1ZVXhgJoR79V510qbJb2tJNVwNbBhalHbwrvSTEeJrD KEyrjnwl8ZxnxVyvHM8JGJQmf9vBNkOsXx6eZpYpInhKQp1N7ys1CwhQzsPfGIsuStz MzlbUNFlA/dklhQgBLIRtJ3tlpCOxb86Hi/bIdaUcY9s+MaUCU1j5+k5dzAW0PSrqVt 2dO7eRczSLCc4julXxu5h5V5SEmkxsOeYKL55GWk5VhArWefQafbWpSJekj1J9BY4Ay XCKsH4yG1appEbgtBGzNcctXD5D0oYWMgFju3ORWrtoKoAOJ47x8wAgSZwLvcrQsi2z 8rCAoaKbpg== Received: by mx.zohomail.com with SMTPS id 17745529481030.9414414434306764; Thu, 26 Mar 2026 12:22:28 -0700 (PDT) From: Rong Zhang Date: Fri, 27 Mar 2026 03:19:50 +0800 Subject: [PATCH 1/4] hwmon: Add label support for 64-bit energy attributes Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260327-b4-hwmon-witrn-v1-1-8d2f1896c045@rong.moe> References: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> To: Guenter Roeck , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-ad80c X-ZohoMailClient: External Since commit 0bcd01f757bc ("hwmon: Introduce 64-bit energy attribute support"), devices can report 64-bit energy values by selecting the sensor type "energy64". However, such sensors can't report their labels since is_string_attr() was not updated to match it. Add label support for 64-bit energy attributes by updating is_string_attr() to match hwmon_energy64 in addition to hwmon_energy. Signed-off-by: Rong Zhang --- drivers/hwmon/hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 9695dca62a7e..6812d1fd7c28 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -505,6 +505,7 @@ static bool is_string_attr(enum hwmon_sensor_types type= , u32 attr) (type =3D=3D hwmon_curr && attr =3D=3D hwmon_curr_label) || (type =3D=3D hwmon_power && attr =3D=3D hwmon_power_label) || (type =3D=3D hwmon_energy && attr =3D=3D hwmon_energy_label) || + (type =3D=3D hwmon_energy64 && attr =3D=3D hwmon_energy_label) || (type =3D=3D hwmon_humidity && attr =3D=3D hwmon_humidity_label) || (type =3D=3D hwmon_fan && attr =3D=3D hwmon_fan_label); } --=20 2.53.0 From nobody Thu Apr 2 19:02:13 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5E16B3A5E82; Thu, 26 Mar 2026 19:22:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552971; cv=pass; b=G10bm+xqy2ORA+6OW+kc8gBqhtFT66hrugP7VMEUsAoFhOl6iTv0LsGy4WGWL4C2LFNSedgBuegjDIwNuo+QrYy4YkV2aCCiykiqzZpxItqJ4ceQe2RimDM3kMBCgP/lR7r2USyrlOfXd7Bx4HCu7waJIBBpfoKRmd9jP2TGI1Q= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552971; c=relaxed/simple; bh=YQ2TsDAXwhLYKHSU70OsI0vAnNLbCizNN03jzBterjc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=m09K2Iw83G0XcGaOYkBeu/g6zTmsgcFNDwoP1B+tp36zxQI9HaaP11ZccQiwtskpIjCDHFLp/j3GAbbnZnkqWPOBOv6sYBU4n7NzDPCK1rp2av/1AwbmQzTVH1jj/3ex29HIOycK/m8DU1jt1M+Pkhr494Y4QLyJNP4bt3hA9mQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=moJgVtnS; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="moJgVtnS" ARC-Seal: i=1; a=rsa-sha256; t=1774552952; cv=none; d=zohomail.com; s=zohoarc; b=MItXoy2w4CAbazyxo/w6mTl3q50M/VHpERN+jUUQYE1PbV7xn2odw0gXyROTeagBvvXOAaef+T7UEDU4gEdPRg2MyYShlrPPYkwULb2WdFQAqH9x7fpu/WMmqiKKN3cb8lb/BQD8ebYq3BAbYI8wFILMsyhlzKs3iTAyENgOsFw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1774552952; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=KABKeWXmWNy6xZq4X48vkXEBYxA09Dc1PuIQoMMCq5A=; b=Gan1W6gnqmKSPOcD9Ez4QvednztXkKTsWs3AdMYEPeZT68vEJ05/dIVx+1AEhv9ig310zUdILLxWsOCZwPMhL1Ov/JwSMY59ImyFMNgSvIQKbFp0+3GO5oKZu7GxLWxkikLuS/ZK10Z1sjc11FOpALnG/VMscTyx8lqh4G20SdY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1774552952; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:References:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=KABKeWXmWNy6xZq4X48vkXEBYxA09Dc1PuIQoMMCq5A=; b=moJgVtnSP2koK1UkqU986ubo02wK+gfnIqJTbg498RHhAALiraXEfUJxmPohFoz/ kvQkE5y6VHy2a6oqssBYmPfJ/6mt1DgbiBoS65f6WsOw2+ZMyvmChsyJKFljSNn1wnk M0IOJQzml3S1z57D0uRBMOiK6qSeokrKhtv7JQYplyGdyIjWDyoNm+94nwzByOh2gFK PSIS8uky2oDFf2T2Hb4peiHRmYnqCtWBTJ6TripxR0fvKvF7U95sv2H9wY0DzFS4q59 2vJmWE/q4cqu3AFUTTJGwbNFBsx0DukqMATuwtpJzdpEN9wID+8yJ7gtgD4vGbikQa3 u3rz+iJLSw== Received: by mx.zohomail.com with SMTPS id 1774552951116471.509015632529; Thu, 26 Mar 2026 12:22:31 -0700 (PDT) From: Rong Zhang Date: Fri, 27 Mar 2026 03:19:51 +0800 Subject: [PATCH 2/4] hwmon: New helper module for floating-point to integer conversions Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260327-b4-hwmon-witrn-v1-2-8d2f1896c045@rong.moe> References: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> To: Guenter Roeck , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-ad80c X-ZohoMailClient: External Some devices report sensor values in IEEE-754 float (binary32) format. Their drivers must perform floating-point number to integer conversions to provide hwmon channels. Meanwhile, some of these devices also report accumulative float values, and simple division or multiplication turns them into useful hwmon channel. Add a new helper module called "hwmon-fp" with float-to-s64/long conversion, multification and division methods, so that these devices can be supported painlessly. Signed-off-by: Rong Zhang --- drivers/hwmon/Kconfig | 3 + drivers/hwmon/Makefile | 1 + drivers/hwmon/hwmon-fp.c | 262 +++++++++++++++++++++++++++++++++++++++++++= ++++ drivers/hwmon/hwmon-fp.h | 212 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 478 insertions(+) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 328867242cb3..7ad909033e79 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -25,6 +25,9 @@ menuconfig HWMON =20 if HWMON =20 +config HWMON_FP + tristate + config HWMON_VID tristate =20 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5833c807c688..6dba69f712be 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -4,6 +4,7 @@ # =20 obj-$(CONFIG_HWMON) +=3D hwmon.o +obj-$(CONFIG_HWMON_FP) +=3D hwmon-fp.o obj-$(CONFIG_HWMON_VID) +=3D hwmon-vid.o =20 # ACPI drivers diff --git a/drivers/hwmon/hwmon-fp.c b/drivers/hwmon/hwmon-fp.c new file mode 100644 index 000000000000..2ad636129a0c --- /dev/null +++ b/drivers/hwmon/hwmon-fp.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Floating-point number to integer conversions + * + * Currently, only float (binary32) is supported. + * + * Copyright (c) 2026 Rong Zhang + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwmon-fp.h" + +#define FLOAT_SIGN_MASK BIT(31) +#define FLOAT_EXPONENT_MASK GENMASK(30, 23) +#define FLOAT_MANTISSA_MASK GENMASK(22, 0) +#define FLOAT_EXPONENT_OFFSET 127 +#define FLOAT_FRACTION_Q 23 +#define FLOAT_IMPLICIT_BIT BIT(23) + +#define HWMON_FP_SCALE_MAGNIFY_SHIFT_L 6 + +struct float_struct { + u32 significand; + s16 shift_r; /* =3D Q - exponent */ + bool sign; + bool inf; /* See below. */ +}; + +/* + * The sign of a floating-point number carries significant information, + * return a saturated value for infinity so its sign is retained. + */ +static inline int hwmon_fp_infinity_to_s64(bool sign, s64 *val) +{ + *val =3D sign ? S64_MIN : S64_MAX; + return 0; +} + +static int hwmon_fp_parse_float(u32 flt, struct float_struct *fs) +{ + u32 mantissa =3D FIELD_GET(FLOAT_MANTISSA_MASK, flt); + u8 exponent =3D FIELD_GET(FLOAT_EXPONENT_MASK, flt); + bool sign =3D FIELD_GET(FLOAT_SIGN_MASK, flt); + + if (unlikely(exponent =3D=3D FLOAT_EXPONENT_MASK >> FLOAT_FRACTION_Q)) { + if (mantissa !=3D 0) /* NaN */ + return -EINVAL; + + /* Infinity */ + fs->significand =3D HWMON_FP_FLOAT_SIGNIFICAND_MAX; /* Distinguish from = fs(zero). */ + fs->shift_r =3D 0; + fs->sign =3D sign; + fs->inf =3D true; + + return 0; + } + + fs->sign =3D sign; + fs->inf =3D false; + + if (likely(exponent !=3D 0)) { + /* Normal */ + fs->significand =3D (FLOAT_IMPLICIT_BIT | mantissa); + fs->shift_r =3D FLOAT_FRACTION_Q - (exponent - FLOAT_EXPONENT_OFFSET); + } else if (unlikely(mantissa !=3D 0)) { /* exponent =3D=3D 0 && mantissa = !=3D 0 */ + /* Subnormal */ + fs->significand =3D mantissa; + fs->shift_r =3D FLOAT_FRACTION_Q - (1 - FLOAT_EXPONENT_OFFSET); + } else { /* exponent =3D=3D 0 && mantissa =3D=3D 0 */ + /* Zero */ + fs->significand =3D 0; /* Only fs(zero) has fs->significand =3D=3D 0. */ + fs->shift_r =3D 0; + } + + return 0; +} + +static int hwmon_fp_raw_to_s64(u64 significand, int shift_r, bool sign, s6= 4 *val) +{ + u64 temp; + + if (unlikely(shift_r >=3D 64) || significand =3D=3D 0) { + *val =3D 0; + return 0; + } + + if (shift_r < 0) { + /* + * Left shift: + * + * (significand * 2^-Q) * 2^exponent + * =3D significand * 2^(exponent - Q) + * =3D significand * 2^-shift_r + * =3D significand << -shift_r + */ + shift_r =3D -shift_r; + temp =3D significand << shift_r; + + if (unlikely(temp >> shift_r !=3D significand)) + return hwmon_fp_infinity_to_s64(sign, val); + } else if (shift_r =3D=3D 0) { + temp =3D significand; + } else { /* shift_r > 0 */ + /* + * Right shift: + * + * (significand * 2^-Q) * 2^exponent + * =3D significand / 2^(Q - exponent) + * =3D significand / 2^shift_r + * =3D significand >> shift_r + */ + temp =3D significand >> shift_r; + + /* Round to nearest. */ + temp +=3D !!(significand & BIT_U64(shift_r - 1)); + } + + if (unlikely((s64)temp < 0)) + return hwmon_fp_infinity_to_s64(sign, val); + + *val =3D (sign ? -1 : 1) * (s64)temp; + return 0; +} + +static int __hwmon_fp_float_to_s64_unsafe(const struct float_struct *fs, u= 32 scale, s64 *val) +{ + if (unlikely(fs->inf)) + return hwmon_fp_infinity_to_s64(fs->sign, val); + + return hwmon_fp_raw_to_s64((u64)scale * (u64)fs->significand, + fs->shift_r, fs->sign, val); +} + +int hwmon_fp_float_to_s64_unsafe(u32 flt, u32 scale, s64 *val) +{ + struct float_struct fs; + int ret; + + ret =3D hwmon_fp_parse_float(flt, &fs); + if (ret) + return ret; + + return __hwmon_fp_float_to_s64_unsafe(&fs, scale, val); +} +EXPORT_SYMBOL_GPL(hwmon_fp_float_to_s64_unsafe); + +static int __hwmon_fp_mul_to_s64_unsafe(const struct float_struct *fs1, + const struct float_struct *fs2, + u32 scale_ntz, ulong scale_ctz, s64 *val) +{ + bool sign =3D fs1->sign ^ fs2->sign; + u64 scaled_significand; + int shift_r; + + if (unlikely((fs1->inf && fs2->significand =3D=3D 0) || (fs1->significand= =3D=3D 0 && fs2->inf))) + return -EINVAL; + + if (unlikely(fs1->inf || fs2->inf)) + return hwmon_fp_infinity_to_s64(sign, val); + + if (fs1->significand =3D=3D 0 || fs2->significand =3D=3D 0) { + *val =3D 0; + return 0; + } + + /* + * scale_ntz * 2^scale_ctz * significand1 * 2^-shift_r1 * significand2 = * 2^-shift_r2 + * =3D scale_ntz * significand1 * significand2 * 2^-(shift_r1 + shift_r2 = - scale_ctz) + * =3D (scale_ntz * significand1 * significand2) >> (shift_r1 + shift_r2 = - scale_ctz) + */ + scaled_significand =3D (u64)scale_ntz * (u64)fs1->significand * (u64)fs2-= >significand; + shift_r =3D fs1->shift_r + fs2->shift_r - scale_ctz; + + return hwmon_fp_raw_to_s64(scaled_significand, shift_r, sign, val); +} + +int hwmon_fp_mul_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale, ulong scale_= ctz, s64 *val) +{ + struct float_struct fs1, fs2; + int ret; + + ret =3D hwmon_fp_parse_float(flt1, &fs1) || hwmon_fp_parse_float(flt2, &f= s2); + if (ret) + return ret; + + return __hwmon_fp_mul_to_s64_unsafe(&fs1, &fs2, scale, scale_ctz, val); +} +EXPORT_SYMBOL_GPL(hwmon_fp_mul_to_s64_unsafe); + +static int __hwmon_fp_div_to_s64_unsafe(const struct float_struct *fs1, + const struct float_struct *fs2, + u32 scale, bool div0_ok, s64 *val) +{ + bool sign =3D fs1->sign ^ fs2->sign; + u64 scaled_significand; + int shift_r; + + if (unlikely(fs1->inf && fs2->inf)) + return -EINVAL; + + if (fs2->significand =3D=3D 0) { + if (div0_ok) { + *val =3D 0; + return 0; + } + return -EINVAL; + } + + if (unlikely(fs1->inf)) + return hwmon_fp_infinity_to_s64(sign, val); + + if (unlikely(fs2->inf) || fs1->significand =3D=3D 0) { + *val =3D 0; + return 0; + } + + /* + * Make the dividend as large as possible to improve accuracy, otherwise + * the divide-and-right-shift procedure may produce an inaccurate result. + * + * scale * (significand1 * 2^-shift_r1) / (significand2 * 2^-shift_r2) + * =3D scale * 2^6 * 2^-6 * (significand1 * 2^-shift_r1) / (significand2 = * 2^-shift_r2) + * =3D (((scale * 2^6) * significand1) / significand2) * 2^-(shift_r1 - s= hift_r2 + 6) + * =3D (((scale << 6) * significand1) / significand2) >> (shift_r1 - shif= t_r2 + 6) + * + * This will never overflow: (2^32 - 1) * 2^6 * (2^24 - 1) < (2^62 - 1). + */ + scaled_significand =3D ((u64)scale << HWMON_FP_SCALE_MAGNIFY_SHIFT_L) * (= u64)fs1->significand; + scaled_significand =3D + (scaled_significand + (u64)fs2->significand / 2) / (u64)fs2->significand; + + shift_r =3D fs1->shift_r - fs2->shift_r + HWMON_FP_SCALE_MAGNIFY_SHIFT_L; + + return hwmon_fp_raw_to_s64(scaled_significand, shift_r, sign, val); +} + +int hwmon_fp_div_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale, bool div0_ok= , s64 *val) +{ + struct float_struct fs1, fs2; + int ret; + + ret =3D hwmon_fp_parse_float(flt1, &fs1) || hwmon_fp_parse_float(flt2, &f= s2); + if (ret) + return ret; + + return __hwmon_fp_div_to_s64_unsafe(&fs1, &fs2, scale, div0_ok, val); +} +EXPORT_SYMBOL_GPL(hwmon_fp_div_to_s64_unsafe); + +MODULE_AUTHOR("Rong Zhang "); +MODULE_DESCRIPTION("hwmon floating-point number to integer conversions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/hwmon-fp.h b/drivers/hwmon/hwmon-fp.h new file mode 100644 index 000000000000..55d6a5021535 --- /dev/null +++ b/drivers/hwmon/hwmon-fp.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Floating-point number to integer conversions + * + * Copyright (c) 2026 Rong Zhang + */ + +#ifndef __HWMON_FP_H__ +#define __HWMON_FP_H__ + +#include +#include +#include +#include +#include +#include +#include + +int hwmon_fp_float_to_s64_unsafe(u32 flt, u32 scale, s64 *val); +int hwmon_fp_mul_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale_ntz, ulong sc= ale_ctz, s64 *val); +int hwmon_fp_div_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale, bool div0_ok= , s64 *val); + +#define HWMON_FP_FLOAT_SIGNIFICAND_BITS 24 +#define HWMON_FP_FLOAT_SIGNIFICAND_MAX BIT(HWMON_FP_FLOAT_SIGNIFICAND_BITS) + +#define HWMON_FP_MUL_SCALE_MAX BIT(64 - HWMON_FP_FLOAT_SIGNIFICAND_BITS *= 2) + +static inline int __hwmon_fp_check_scale(u32 scale) +{ + return WARN_ON(scale <=3D 0) ? -EINVAL : 0; +} + +#define hwmon_fp_check_scale(scale) \ + __builtin_choose_expr(__is_constexpr(scale), \ + BUILD_BUG_ON_ZERO((scale) <=3D 0), \ + __hwmon_fp_check_scale(scale)) + +#define HWMON_FP_SCALE_IN MILLI /* millivolt (mV) */ +#define HWMON_FP_SCALE_TEMP MILLI /* millidegree Celsius (m=C2=B0C) */ +#define HWMON_FP_SCALE_CURR MILLI /* milliampere (mA) */ +#define HWMON_FP_SCALE_POWER MICRO /* microWatt (uW) */ +#define HWMON_FP_SCALE_ENERGY MICRO /* microJoule (uJ) */ + +/** + * hwmon_fp_float_to_s64() - Convert a float (binary32) number into a sign= ed + * 64-bit integer. + * @flt: Float (binary32) number. + * @scale: Scale factor. + * @val: Pointer to store the converted value. + * + * Special case: + * inf -> S64_MAX or S64_MIN + * NaN -> -EINVAL; + * (overflow) -> S64_MAX or S64_MIN + * (underflow) -> 0 + * + * Return: 0 on success, or an error code. + */ +#define hwmon_fp_float_to_s64(flt, scale, val) \ + (hwmon_fp_check_scale(scale) || \ + hwmon_fp_float_to_s64_unsafe(flt, scale, val)) + +/* + * Handling multification is very tricky, as large scale factors must not = lead + * to overflow. Fortunately, cutting off all trailing zeros and restoring = them + * while right shifting is enough reduce the scale factor used in + * multiplication to a small enough value. + */ +static inline int __hwmon_fp_mul_to_s64(u32 flt1, u32 flt2, u32 scale, s64= *val) +{ + ulong scale_ctz; + + if (WARN_ON(scale <=3D 0)) + return -EINVAL; + + scale_ctz =3D __ffs(scale); + scale >>=3D scale_ctz; + + if (WARN_ON(scale >=3D HWMON_FP_MUL_SCALE_MAX)) + return -EINVAL; + + return hwmon_fp_mul_to_s64_unsafe(flt1, flt2, scale, scale_ctz, val); +} + +#define __hwmon_fp_mul_to_s64_const(flt1, flt2, scale, val) \ +({ \ + u32 _scale_ntz =3D (scale); \ + ulong _scale_ctz; \ + \ + BUILD_BUG_ON(_scale_ntz <=3D 0); \ + \ + _scale_ctz =3D __builtin_ctzl(_scale_ntz); \ + _scale_ntz >>=3D _scale_ctz; \ + \ + BUILD_BUG_ON(_scale_ntz >=3D HWMON_FP_MUL_SCALE_MAX); \ + \ + hwmon_fp_mul_to_s64_unsafe(flt1, flt2, _scale_ntz, _scale_ctz, val); \ +}) + +/** + * hwmon_fp_mul_to_s64() - Multiply two float (binary32) numbers and conve= rt the + * product into a signed 64-bit integer. + * @flt1: Multiplicand stored in float (binary32) format. + * @flt2: Multiplier stored in float (binary32) format. + * @scale: Scale factor. + * @val: Pointer to store the product. + * + * Calculate @scale * @flt1 * @flt2. + * + * Special case: + * 0 * inf -> -EINVAL + * x * inf -> S64_MAX or S64_MIN + * x * 0 -> 0 + * + * Return: 0 on success, or an error code. + */ +#define hwmon_fp_mul_to_s64(flt1, flt2, scale, val) \ + __builtin_choose_expr(__is_constexpr(scale), \ + __hwmon_fp_mul_to_s64_const(flt1, flt2, scale, val), \ + __hwmon_fp_mul_to_s64(flt1, flt2, scale, val)) + +/** + * hwmon_fp_div_to_s64() - Divide two float (binary32) numbers and convert= the + * quotient into a signed 64-bit integer. + * @flt1: Dividend stored in float (binary32) format. + * @flt2: Divisor stored in float (binary32) format. + * @scale: Scale factor. + * @div0_ok: If true, return 0 when @flt2 is 0. Otherwise, -EINVAL is retu= rned. + * @val: Pointer to store the quotient. + * + * Calculate @scale * (@flt1 / @flt2). + * + * Special case: + * inf / inf -> -EINVAL + * inf / x -> S64_MAX or S64_MIN + * x / 0 -> See div0_ok + * x / inf -> 0 + * 0 / x -> 0 + * + * Return: 0 on success, or an error code. + */ +#define hwmon_fp_div_to_s64(flt1, flt2, scale, div0_ok, val) \ + (hwmon_fp_check_scale(scale) || \ + hwmon_fp_div_to_s64_unsafe(flt1, flt2, scale, div0_ok, val)) + +#if BITS_PER_LONG =3D=3D 64 + +static_assert(sizeof(long) =3D=3D sizeof(s64)); + +static inline long hwmon_fp_s64_to_long(s64 val64) +{ + return val64; +} + +# define hwmon_fp_float_to_long(flt, scale, val) \ + hwmon_fp_float_to_s64(flt, scale, (s64 *)val) +# define hwmon_fp_div_to_long(flt1, flt2, scale, div0_ok, val) \ + hwmon_fp_div_to_s64(flt1, flt2, scale, div0_ok, (s64 *)val) +# define hwmon_fp_mul_to_long(flt1, flt2, scale, val) \ + hwmon_fp_mul_to_s64(flt1, flt2, scale, (s64 *)val) + +#else /* BITS_PER_LONG =3D=3D 64 */ + +static inline long hwmon_fp_s64_to_long(s64 val64) +{ + if (unlikely(val64 > LONG_MAX)) + return LONG_MAX; + else if (unlikely(val64 < LONG_MIN)) + return LONG_MIN; + else + return val64; +} + +# define hwmon_fp_float_to_long(flt, scale, val) \ +({ \ + s64 _val64; \ + int _ret; \ + \ + _ret =3D hwmon_fp_float_to_s64(flt, scale, &_val64); \ + if (!_ret) \ + *(val) =3D hwmon_fp_s64_to_long(_val64); \ + \ + _ret; \ +}) + +# define hwmon_fp_div_to_long(flt1, flt2, scale, div0_ok, val) \ +({ \ + s64 _val64; \ + int _ret; \ + \ + _ret =3D hwmon_fp_div_to_s64(flt1, flt2, scale, div0_ok, &_val64); \ + if (!_ret) \ + *(val) =3D hwmon_fp_s64_to_long(_val64); \ + \ + _ret; \ +}) + +# define hwmon_fp_mul_to_long(flt1, flt2, scale, val) \ +({ \ + s64 _val64; \ + int _ret; \ + \ + _ret =3D hwmon_fp_mul_to_s64(flt1, flt2, scale, &_val64); \ + if (!_ret) \ + *(val) =3D hwmon_fp_s64_to_long(_val64); \ + \ + _ret; \ +}) + +#endif /* BITS_PER_LONG =3D=3D 64 */ + +#endif /* __HWMON_FP_H__ */ --=20 2.53.0 From nobody Thu Apr 2 19:02:13 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 610683A3E9F; Thu, 26 Mar 2026 19:22:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552974; cv=pass; b=NyLbdzI1i4dsIQrcs/IW24SCS6dSVYeR3DvVcBPi4Z20Wg7I/V+NH4XuCMzU4YfgZ/y9HZAQUxof3umTBvqt0jLfEiZPaWbh1m4eAWWeTtQoqkgF2zLoECllmaeizqAf/rGIqt1vbu8wUClAxVCVmqH8hVTJBHrnM/CZ4PHU2MU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552974; c=relaxed/simple; bh=dGXH/4FGhXaixsvF3xJIyPFZcr8nQlHD5oIhgKk/fXg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ti9Q4XACnqKCH8ObECMGMVuvT1GAwy5Y6CLRfgZ27GUnGMcqqjCpBYc8oTWj5gDBbLL6X7Q1p7LnI2FmHTX5BMJJ1noGsAwvooB1ImkbjSydCcDxFO5sALJWphmHlYDXGgnTdb+9CoRiLfsSL+yQ+cx+tj3BNROltjpp7YNYw6Q= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=Vff9tpT4; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="Vff9tpT4" ARC-Seal: i=1; a=rsa-sha256; t=1774552956; cv=none; d=zohomail.com; s=zohoarc; b=Fneb1ui6m0f2SLxGY24TiCwkNNyIg45pXKUMXWGaErQ1WuvMTayD27bP6vj6SD4wrGX40oDhRomxbQLFm44bpHtePGz68M6Wy3Gl4/IIwcSclPL6XUayylGiipqg0zG6+A39+B39c/xTuTJvWCjvGwjDYSX2Ps26aJwymQuqyKk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1774552956; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=a047eY7JtglFt604o6sBwAdZjfVRTjwQGri385hCBT0=; b=ZgMSFXc2fXOmwKhZgGKznyGJXdD3I5XbulIxcKuFaTkAey7aNiBeKd/+QF3IYQCa5SMa/lo2ynKJ6sq/HWotNjXc3NMiElfPyUTGJNQGLBoqXLbiqTBmBPTG+EfR6qHo/qv0pDr8SYCnhSprRh69m0hfuX+z9mZbasBFxA1FAEU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1774552956; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:References:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=a047eY7JtglFt604o6sBwAdZjfVRTjwQGri385hCBT0=; b=Vff9tpT4R5ps/PZtqBDJOJT4j9yKo/2ynacIlypIwd+V17T+InPWylkoPbUsVKu+ HBSo7g+IyssmHG7FY/JeWGIULuWxk/u3XUWJLe9DkmsumYRhR0TxsvlQzHNF2RcYRIe ZJaYyynks+W4Z5ems8lmFS0mNcMl/HFgr2DTtqf9Q78rPyEIOKf6rZeI0mk2vb6YlOq uyr4Oz3OtarOjlTliGiLh9y75lQVScAbUdQn+KGoqadAtqVIlFzclPOrcI9k0gPJ5vE VXZBwrkRw9NFi0xFk4fERoBq281x31R8GT80KLvMLBi1POCa9jB8EzIW8//DMzQ/WJb e+2KKQfwnw== Received: by mx.zohomail.com with SMTPS id 177455295434189.09367790827082; Thu, 26 Mar 2026 12:22:34 -0700 (PDT) From: Rong Zhang Date: Fri, 27 Mar 2026 03:19:52 +0800 Subject: [PATCH 3/4] hwmon: Add barebone HID driver for WITRN Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260327-b4-hwmon-witrn-v1-3-8d2f1896c045@rong.moe> References: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> To: Guenter Roeck , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-ad80c X-ZohoMailClient: External WITRN produces a series of devices to monitor power characteristics of USB connections and display those on a on-device display. Most of them contain an additional port which exposes the measurements via USB HID. Add a barebone HID driver to collect these measurements. The monitoring support can be implemented later. Developed and tested with WITRN K2 USB-C tester. Signed-off-by: Rong Zhang --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/witrn.rst | 23 ++++ MAINTAINERS | 7 ++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/witrn.c | 268 ++++++++++++++++++++++++++++++++++++++= ++++ 6 files changed, 310 insertions(+) diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b2ca8513cfcd..f1f2b599c76b 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -276,6 +276,7 @@ Hardware Monitoring Kernel Drivers w83795 w83l785ts w83l786ng + witrn wm831x wm8350 xgene-hwmon diff --git a/Documentation/hwmon/witrn.rst b/Documentation/hwmon/witrn.rst new file mode 100644 index 000000000000..e64c527928d0 --- /dev/null +++ b/Documentation/hwmon/witrn.rst @@ -0,0 +1,23 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +Kernel driver witrn +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Supported chips: + + * WITRN K2 + + Prefix: 'witrn' + + Addresses scanned: - + +Author: + + - Rong Zhang + +Description +----------- + +This driver implements support for the WITRN USB tester family. + +The device communicates with the custom protocol over USB HID. diff --git a/MAINTAINERS b/MAINTAINERS index 0481aca2286c..18a1077d38e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28444,6 +28444,13 @@ M: Miloslav Trmac S: Maintained F: drivers/input/misc/wistron_btns.c =20 +WITRN USB TESTER HARDWARE MONITOR DRIVER +M: Rong Zhang +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/witrn.rst +F: drivers/hwmon/witrn.c + WMI BINARY MOF DRIVER M: Armin Wolf R: Thomas Wei=C3=9Fschuh diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7ad909033e79..746184608f81 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2629,6 +2629,16 @@ config SENSORS_W83627EHF This driver can also be built as a module. If so, the module will be called w83627ehf. =20 +config SENSORS_WITRN + tristate "WITRN USB tester" + depends on USB_HID + help + If you say yes here you get support for WITRN USB charging + testers. + + This driver can also be built as a module. If so, the module + will be called witrn. + config SENSORS_WM831X tristate "WM831x PMICs" depends on MFD_WM831X diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 6dba69f712be..f87eb1710974 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -243,6 +243,7 @@ obj-$(CONFIG_SENSORS_VT8231) +=3D vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) +=3D w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) +=3D w83l785ts.o obj-$(CONFIG_SENSORS_W83L786NG) +=3D w83l786ng.o +obj-$(CONFIG_SENSORS_WITRN) +=3D witrn.o obj-$(CONFIG_SENSORS_WM831X) +=3D wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) +=3D wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) +=3D xgene-hwmon.o diff --git a/drivers/hwmon/witrn.c b/drivers/hwmon/witrn.c new file mode 100644 index 000000000000..e8713da6de5a --- /dev/null +++ b/drivers/hwmon/witrn.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * witrn - Driver for WITRN USB charging testers + * + * Copyright (C) 2026 Rong Zhang + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "witrn" +#define WITRN_EP_CMD_OUT 0x01 +#define WITRN_EP_DATA_IN 0x81 + +#define WITRN_REPORT_SZ 64 + +/* flags */ +#define WITRN_HID_OPENED 0 + +/* + * The device sends reports every 10ms (100Hz!) once it's opened, which is + * really annoying and produces a lot of irq noise. + * + * Unfortunately, the device doesn't provide any command to start/stop rep= orting + * on demand -- it simply spams reports blindly. The only way to stop repo= rting + * is to close the HID device (i.e., to stop IN URB (re)submission). + * + * Let's close the HID device if the device has not been accessed for a wh= ile. + */ +#define PAUSE_TIMEOUT secs_to_jiffies(8) +#define UP_TO_DATE_TIMEOUT msecs_to_jiffies(100) + +enum witrn_report_type { + WITRN_PD =3D 0xfe, + WITRN_SENSOR =3D 0xff, +}; + +struct witrn_sensor { + __le16 record_threshold; /* mA */ + __le32 record_charge; /* Ah (float) */ + __le32 record_energy; /* Wh (float) */ + __le32 record_time; /* s */ + __le32 uptime; /* s */ + __le32 vdp; /* V (float) */ + __le32 vdm; /* V (float) */ + u8 __unknown[4]; + __le32 temp_ntc; /* Celsius (float) */ + __le32 vbus; /* V (float) */ + __le32 ibus; /* A (float) */ + u8 record_group; /* 0: group 1 on device, ... */ + u8 vcc1; /* dV */ + u8 vcc2; /* dV */ +} __packed; + +struct witrn_report { + u8 report_type; + u8 __unknown_0[11]; + + struct witrn_sensor sensor; + + u8 __unknown_1[7]; +} __packed; +static_assert(sizeof(struct witrn_report) =3D=3D WITRN_REPORT_SZ); + +struct witrn_priv { + struct hid_device *hdev; + + struct work_struct pause_work; + + unsigned long flags; + + spinlock_t lock; /* Protects members below */ + + struct completion completion; + unsigned long last_update; /* jiffies */ + unsigned long last_access; /* jiffies */ + + struct witrn_sensor sensor; +}; + +static inline bool sensor_is_outdated(struct witrn_priv *priv) +{ + return time_after(jiffies, priv->last_update + UP_TO_DATE_TIMEOUT); +} + +static inline bool hwmon_is_inactive(struct witrn_priv *priv) +{ + return time_after(jiffies, priv->last_access + PAUSE_TIMEOUT); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D HID =3D=3D=3D=3D=3D=3D=3D=3D */ + +static int witrn_open_hid(struct witrn_priv *priv) +{ + int ret; + + if (test_and_set_bit(WITRN_HID_OPENED, &priv->flags)) + return 0; /* Already opened */ + + hid_dbg(priv->hdev, "opening hid hw\n"); + + ret =3D hid_hw_open(priv->hdev); + if (ret) { + hid_err(priv->hdev, "hid hw open failed with %d\n", ret); + clear_bit(WITRN_HID_OPENED, &priv->flags); + } + + return ret; +} + +static void witrn_close_hid(struct witrn_priv *priv) +{ + if (!test_and_clear_bit(WITRN_HID_OPENED, &priv->flags)) + return; /* Already closed */ + + hid_dbg(priv->hdev, "closing hid hw\n"); + + hid_hw_close(priv->hdev); +} + +static void witrn_pause_hid(struct work_struct *work) +{ + struct witrn_priv *priv =3D container_of(work, struct witrn_priv, pause_w= ork); + + scoped_guard(spinlock, &priv->lock) { + /* Double check. Condition may change after being scheduled. */ + if (!hwmon_is_inactive(priv)) + return; + } + + witrn_close_hid(priv); +} + +static int witrn_raw_event(struct hid_device *hdev, struct hid_report *rep= ort, + u8 *data, int size) +{ + struct witrn_priv *priv =3D hid_get_drvdata(hdev); + const struct witrn_report *wreport; + bool do_pause =3D false; + + /* HIDRAW has opened the device while we are pausing. */ + if (!test_bit(WITRN_HID_OPENED, &priv->flags)) + return 0; + + if (size < WITRN_REPORT_SZ) { + hid_dbg(hdev, "report size mismatch: %d < %d\n", size, WITRN_REPORT_SZ); + return 0; + } + + wreport =3D (const struct witrn_report *)data; + if (wreport->report_type !=3D WITRN_SENSOR) { + hid_dbg(hdev, "report ignored with type 0x%02x", wreport->report_type); + return 0; + } + + scoped_guard(spinlock, &priv->lock) { + priv->last_update =3D jiffies; + do_pause =3D hwmon_is_inactive(priv); + + memcpy(&priv->sensor, &wreport->sensor, sizeof(wreport->sensor)); + complete(&priv->completion); + } + + if (do_pause) + schedule_work(&priv->pause_work); + + return 0; +} + +static int witrn_probe(struct hid_device *hdev, const struct hid_device_id= *id) +{ + struct device *parent =3D &hdev->dev; + struct witrn_priv *priv; + int ret; + + priv =3D devm_kzalloc(parent, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->hdev =3D hdev; + hid_set_drvdata(hdev, priv); + + ret =3D hid_parse(hdev); + if (ret) { + hid_err(hdev, "hid parse failed with %d\n", ret); + return ret; + } + + /* Enable HIDRAW so existing user-space tools can continue to work. */ + ret =3D hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "hid hw start failed with %d\n", ret); + return ret; + } + + spin_lock_init(&priv->lock); + init_completion(&priv->completion); + + INIT_WORK(&priv->pause_work, witrn_pause_hid); + + priv->last_access =3D jiffies; + priv->last_update =3D priv->last_access - UP_TO_DATE_TIMEOUT - 1; + clear_bit(WITRN_HID_OPENED, &priv->flags); + + ret =3D witrn_open_hid(priv); + if (ret) { + hid_hw_stop(hdev); + return ret; + } + + return 0; +} + +static void witrn_remove(struct hid_device *hdev) +{ + struct witrn_priv *priv =3D hid_get_drvdata(hdev); + + witrn_close_hid(priv); + + /* Cancel it after closing HID so that it won't be rescheduled. */ + cancel_work_sync(&priv->pause_work); + + hid_hw_stop(hdev); +} + +static const struct hid_device_id witrn_id_table[] =3D { + { HID_USB_DEVICE(0x0716, 0x5060) }, /* WITRN K2 USB-C tester */ + { } +}; + +MODULE_DEVICE_TABLE(hid, witrn_id_table); + +static struct hid_driver witrn_driver =3D { + .name =3D DRIVER_NAME, + .id_table =3D witrn_id_table, + .probe =3D witrn_probe, + .remove =3D witrn_remove, + .raw_event =3D witrn_raw_event, +}; + +static int __init witrn_init(void) +{ + return hid_register_driver(&witrn_driver); +} + +static void __exit witrn_exit(void) +{ + hid_unregister_driver(&witrn_driver); +} + +/* When compiled into the kernel, initialize after the HID bus */ +late_initcall(witrn_init); +module_exit(witrn_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rong Zhang "); +MODULE_DESCRIPTION("WITRN USB tester driver"); --=20 2.53.0 From nobody Thu Apr 2 19:02:13 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1C3F03A6416; Thu, 26 Mar 2026 19:22:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552977; cv=pass; b=s3D+ijDK/1a2Pn5uQ82Hqopwf7NelK0KQGPsfKyGP7UJyB6jq19Afo5BxYRcS5h1jXrWnFfU8sPRUzTz8RY5zfBYLU60Gy1kktBs/wT0NG2FDD4jPVTxitY5dgKFgXJsVMDn48RXlHzSctNp8ZOvAE5Q+btmpVJ8Bli/LKWsKMk= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774552977; c=relaxed/simple; bh=ElIcgjPTKcKVuJvtUtybqY4ZrX04zTFLaltESqY317I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=W227jzYaR9yD468tAiLEcl5qQsYGMrmRRfTUYHIvB7IVDw6tHPG0vUlbgu3++vf0PTsu8FMCNe7VYxi+Xhaujnx592wAUlgxK4L02EV9ZaQrUyqRx7SQ+NOLxCXCsfpHuqMrXArDn22ts4l/PWWjc/RltQjEQH0a8QsyKUyHCyc= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=PClXMnih; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="PClXMnih" ARC-Seal: i=1; a=rsa-sha256; t=1774552958; cv=none; d=zohomail.com; s=zohoarc; b=AYuKg08mpCVehorbmB4F7w8dHocA+t0uNSyaIUnhGL6OoQrJdK7PvrVawe1lHqM9MrReCuOuoU4EspP8S7VZy6EgU0xjPB6y+r1cLcMMb++AuUgNSwD+FqdOQ12wPhVrtzdwLszBDugUtzP8IS6h9wZL67AqevRARD316g3OTPc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1774552958; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=/DjgRAt09TnKcBLF5rzLUQRU21V0brOez7Wg4AnrajY=; b=cIFjYBHUxhqV4sy7rm5rflfVPfQZ8bkbepdWUFajNkkomReDWt5bsh7JYV1f/GpKbXaulpQvCFiLng6yHP/lKWe3BZGE8ZJtslEP0kAB+1qhLm9MILDvLYYp2q1FG+e36QBFt2O4nmDODv0BjR/DiKXlOjO/ksuTsi06NTr7TUE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1774552958; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:References:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=/DjgRAt09TnKcBLF5rzLUQRU21V0brOez7Wg4AnrajY=; b=PClXMnihnaWUtw9PJ1pVIaI8ZFKNiStICXOpXuzVjWvRRPm387GyR91029Jp0SjR CKLMwqi7Ci9+jbsXDx1Bm1duE7DqURbLJhFDBU5NYHYWAXxRrEJ8hRr2OC9KQGD68Zn Wdsj4VKtJFmnEmboKATgSXyYp3yxshckRV1BvwXmx7IItMjSVsrFw53PLVQQa/xB7hj AFGDkRu0jnFanp/oD/OZ+TpN+9lEpWVEfBvwTPda/kWkRmNooxh+BXwCU8qkDsjohGe RNj4hSI2RCKdpwzhouBCHi615+G7kGWipipID3lpxHiIeB3Mfgg5FetiExVxMoK8Wbl M3rkovfW4g== Received: by mx.zohomail.com with SMTPS id 1774552957703437.16605720448797; Thu, 26 Mar 2026 12:22:37 -0700 (PDT) From: Rong Zhang Date: Fri, 27 Mar 2026 03:19:53 +0800 Subject: [PATCH 4/4] hwmon: (witrn) Add monitoring support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260327-b4-hwmon-witrn-v1-4-8d2f1896c045@rong.moe> References: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe> To: Guenter Roeck , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-ad80c X-ZohoMailClient: External With sensor data collected, they can be exported to userspace via hwmon. Register hwmon driver for witrn, with appropriate channels and attributes. Developed and tested with WITRN K2 USB-C tester. Theoretically this driver should work properly on other models. They can be added to the HID match table too if someone tests the driver with them. Signed-off-by: Rong Zhang --- Documentation/hwmon/witrn.rst | 30 +++ drivers/hwmon/Kconfig | 1 + drivers/hwmon/witrn.c | 423 ++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 454 insertions(+) diff --git a/Documentation/hwmon/witrn.rst b/Documentation/hwmon/witrn.rst index e64c527928d0..f13323fdd9d9 100644 --- a/Documentation/hwmon/witrn.rst +++ b/Documentation/hwmon/witrn.rst @@ -21,3 +21,33 @@ Description This driver implements support for the WITRN USB tester family. =20 The device communicates with the custom protocol over USB HID. + +As current can flow in both directions through the tester the sign of the +channel "curr1_input" (label "IBUS") indicates the direction. + +Sysfs entries +------------- + + =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + Name Label Description + =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + in0_input VBUS Measured VBUS voltage (mV) + in0_average VBUS Calculated average VBUS voltage (mV) + in1_input D+ Measured D+ voltage (mV) + in2_input D- Measured D- voltage (mV) + in3_input CC1 Measured CC1 voltage (mV) + in4_input CC2 Measured CC2 voltage (mV) + cur1_input IBUS Measured VBUS current (mA) + curr1_average IBUS Calculated average VBUS current (mA) + curr1_rated_min IBUS Stop accumulating (recording) below this VB= US current (mA) + power1_input PBUS Calculated VBUS power (uW) + power1_average PBUS Calculated average VBUS power (uW) + energy1_input EBUS Accumulated VBUS energy (uJ) + charge1_input CBUS Accumulated VBUS charge (mC) + temp1_input Thermistor Measured thermistor temperature (m=C2=B0C),= -EXDEV if not connected + record_group ID of the record group for accumulative val= ues + record_time Accumulated time for recording (s), see als= o curr1_rated_min + uptime Accumulated time since the device has been = powered on (s) + =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +All entries are readonly. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 746184608f81..c8b5144707a1 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2632,6 +2632,7 @@ config SENSORS_W83627EHF config SENSORS_WITRN tristate "WITRN USB tester" depends on USB_HID + select HWMON_FP help If you say yes here you get support for WITRN USB charging testers. diff --git a/drivers/hwmon/witrn.c b/drivers/hwmon/witrn.c index e8713da6de5a..f43bdbf13435 100644 --- a/drivers/hwmon/witrn.c +++ b/drivers/hwmon/witrn.c @@ -11,14 +11,19 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include #include =20 +#include "hwmon-fp.h" + #define DRIVER_NAME "witrn" #define WITRN_EP_CMD_OUT 0x01 #define WITRN_EP_DATA_IN 0x81 @@ -74,6 +79,7 @@ struct witrn_report { static_assert(sizeof(struct witrn_report) =3D=3D WITRN_REPORT_SZ); =20 struct witrn_priv { + struct device *hwmon_dev; struct hid_device *hdev; =20 struct work_struct pause_work; @@ -178,6 +184,413 @@ static int witrn_raw_event(struct hid_device *hdev, s= truct hid_report *report, return 0; } =20 +/* =3D=3D=3D=3D=3D=3D=3D=3D HWMON =3D=3D=3D=3D=3D=3D=3D=3D */ + +static int witrn_collect_sensor(struct witrn_priv *priv, struct witrn_sens= or *sensor) +{ + int ret; + + scoped_guard(spinlock, &priv->lock) { + priv->last_access =3D jiffies; + + if (!sensor_is_outdated(priv)) { + memcpy(sensor, &priv->sensor, sizeof(priv->sensor)); + return 0; + } + + reinit_completion(&priv->completion); + } + + ret =3D witrn_open_hid(priv); + if (ret) + return ret; + + ret =3D wait_for_completion_interruptible_timeout(&priv->completion, + UP_TO_DATE_TIMEOUT); + if (ret =3D=3D 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + scoped_guard(spinlock, &priv->lock) + memcpy(sensor, &priv->sensor, sizeof(priv->sensor)); + + return 0; +} + +#define SECS_PER_HOUR 3600ULL +#define WITRN_SCALE_IN_VCC (HWMON_FP_SCALE_IN / DECI) /* dV to mV */ +#define WITRN_SCALE_CHARGE (HWMON_FP_SCALE_CURR * SECS_PER_HOUR) /* Ah to = mC(mAs) */ +#define WITRN_SCALE_ENERGY (HWMON_FP_SCALE_ENERGY * SECS_PER_HOUR) /* Wh t= o uJ(uWs) */ + +static int witrn_read_in(const struct witrn_sensor *sensor, u32 attr, int = channel, long *val) +{ + switch (attr) { + case hwmon_in_input: + switch (channel) { + case 0: + return hwmon_fp_float_to_long(le32_to_cpu(sensor->vbus), + HWMON_FP_SCALE_IN, val); + case 1: + return hwmon_fp_float_to_long(le32_to_cpu(sensor->vdp), + HWMON_FP_SCALE_IN, val); + case 2: + return hwmon_fp_float_to_long(le32_to_cpu(sensor->vdm), + HWMON_FP_SCALE_IN, val); + case 3: + *val =3D sensor->vcc1 * WITRN_SCALE_IN_VCC; + return 0; + case 4: + *val =3D sensor->vcc2 * WITRN_SCALE_IN_VCC; + return 0; + default: + return -EOPNOTSUPP; + } + case hwmon_in_average: + switch (channel) { + case 0: + return hwmon_fp_div_to_long(le32_to_cpu(sensor->record_energy), + le32_to_cpu(sensor->record_charge), + HWMON_FP_SCALE_IN, true, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int witrn_read_curr(const struct witrn_sensor *sensor, u32 attr, in= t channel, long *val) +{ + int ret; + + switch (attr) { + case hwmon_curr_input: + switch (channel) { + case 0: + return hwmon_fp_float_to_long(le32_to_cpu(sensor->ibus), + HWMON_FP_SCALE_CURR, val); + default: + return -EOPNOTSUPP; + } + case hwmon_curr_average: + switch (channel) { + case 0: { + s64 record_time =3D le32_to_cpu(sensor->record_time); + s64 capacity; /* mC(mAs) */ + + if (record_time =3D=3D 0) { + *val =3D 0; + return 0; + } + + ret =3D hwmon_fp_float_to_s64(le32_to_cpu(sensor->record_charge), + WITRN_SCALE_CHARGE, &capacity); + if (ret) + return ret; + + /* mC(mAs) / s =3D mA */ + *val =3D hwmon_fp_s64_to_long(capacity / record_time); + return 0; + } + default: + return -EOPNOTSUPP; + } + case hwmon_curr_rated_min: + switch (channel) { + case 0: + *val =3D le16_to_cpu(sensor->record_threshold); /* already in mA */ + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int witrn_read_power(const struct witrn_sensor *sensor, u32 attr, i= nt channel, long *val) +{ + int ret; + + switch (attr) { + case hwmon_power_input: + switch (channel) { + case 0: + /* + * The device provides 1e-5 precision. + * + * Though userspace programs can calculate (VBUS * IBUS) + * themselves, this channel is provided for convenience + * and accuracy. + * + * E.g., when VBUS =3D 5.00049V and IBUS =3D 0.50049A, + * userspace calculates 5.000V * 0.500A =3D 2.500000W, + * while this channel reports 2.502695W. + */ + return hwmon_fp_mul_to_long(le32_to_cpu(sensor->vbus), + le32_to_cpu(sensor->ibus), + HWMON_FP_SCALE_POWER, val); + default: + return -EOPNOTSUPP; + } + case hwmon_power_average: + switch (channel) { + case 0: { + s64 record_time =3D le32_to_cpu(sensor->record_time); + s64 energy; /* uJ(uWs) */ + + if (record_time =3D=3D 0) { + *val =3D 0; + return 0; + } + + ret =3D hwmon_fp_float_to_s64(le32_to_cpu(sensor->record_energy), + WITRN_SCALE_ENERGY, &energy); + if (ret) + return ret; + + /* uJ(uWs) / s =3D uW */ + *val =3D hwmon_fp_s64_to_long(energy / record_time); + return 0; + } + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int witrn_read_temp(const struct witrn_sensor *sensor, u32 attr, in= t channel, long *val) +{ + int ret; + + switch (attr) { + case hwmon_temp_input: + switch (channel) { + case 0: + ret =3D hwmon_fp_float_to_long(le32_to_cpu(sensor->temp_ntc), + HWMON_FP_SCALE_TEMP, val); + + /* + * The thermistor (NTC, B=3D3435, T0=3D25=C2=B0C, R0=3D10kohm) is an op= tional + * addon. When it's missing, an extremely cold temperature + * (-50=C2=B0C - -80=C2=B0C) is reported as the device deduced a very l= arge + * resistance value (~500Kohm - ~5Mohm). + * + * We choose -40=C2=B0C (~250kohm) as the threshold to determine whether + * the thermistor is connected. + * + * The addon can be connected to the device after the device being + * connected to the PC, so we can't use is_visible to hide it. + */ + if (!ret && *val < -40L * (long)HWMON_FP_SCALE_TEMP) + return -EXDEV; + + return ret; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int witrn_read_energy(const struct witrn_sensor *sensor, u32 attr, = int channel, s64 *val) +{ + switch (attr) { + case hwmon_energy_input: + switch (channel) { + case 0: + return hwmon_fp_float_to_s64(le32_to_cpu(sensor->record_energy), + WITRN_SCALE_ENERGY, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int witrn_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct witrn_priv *priv =3D dev_get_drvdata(dev); + struct witrn_sensor sensor; + int ret; + + ret =3D witrn_collect_sensor(priv, &sensor); + if (ret) + return ret; + + switch (type) { + case hwmon_in: + return witrn_read_in(&sensor, attr, channel, val); + case hwmon_curr: + return witrn_read_curr(&sensor, attr, channel, val); + case hwmon_power: + return witrn_read_power(&sensor, attr, channel, val); + case hwmon_temp: + return witrn_read_temp(&sensor, attr, channel, val); + case hwmon_energy64: + return witrn_read_energy(&sensor, attr, channel, (s64 *)val); + default: + return -EOPNOTSUPP; + } +} + +static int witrn_read_string(struct device *dev, enum hwmon_sensor_types t= ype, + u32 attr, int channel, const char **str) +{ + static const char * const in_labels[] =3D { + "VBUS", + "D+", + "D-", + "CC1", + "CC2", + }; + static const char * const curr_labels[] =3D { + "IBUS", /* VBUS current */ + }; + static const char * const power_labels[] =3D { + "PBUS", /* VBUS power */ + }; + static const char * const energy_labels[] =3D { + "EBUS", /* VBUS energy */ + }; + static const char * const temp_labels[] =3D { + "Thermistor", + }; + + if (type =3D=3D hwmon_in && attr =3D=3D hwmon_in_label && + channel < ARRAY_SIZE(in_labels)) { + *str =3D in_labels[channel]; + } else if (type =3D=3D hwmon_curr && attr =3D=3D hwmon_curr_label && + channel < ARRAY_SIZE(curr_labels)) { + *str =3D curr_labels[channel]; + } else if (type =3D=3D hwmon_power && attr =3D=3D hwmon_power_label && + channel < ARRAY_SIZE(power_labels)) { + *str =3D power_labels[channel]; + } else if (type =3D=3D hwmon_energy64 && attr =3D=3D hwmon_energy_label && + channel < ARRAY_SIZE(energy_labels)) { + *str =3D energy_labels[channel]; + } else if (type =3D=3D hwmon_temp && attr =3D=3D hwmon_temp_label && + channel < ARRAY_SIZE(temp_labels)) { + *str =3D temp_labels[channel]; + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_channel_info *const witrn_info[] =3D { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_AVERAGE, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_AVERAGE | HWMON_C_RATED_MIN), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_LABEL | HWMON_P_AVERAGE), + HWMON_CHANNEL_INFO(energy64, + HWMON_E_INPUT | HWMON_E_LABEL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_ops witrn_hwmon_ops =3D { + .visible =3D 0444, /* Nothing is tunable from PC :-( */ + .read =3D witrn_read, + .read_string =3D witrn_read_string, +}; + +static const struct hwmon_chip_info witrn_chip_info =3D { + .ops =3D &witrn_hwmon_ops, + .info =3D witrn_info, +}; + +enum witrn_attr_channel { + ATTR_CHARGE, + ATTR_RECORD_GROUP, + ATTR_RECORD_TIME, + ATTR_UPTIME, +}; + +static ssize_t witrn_attr_show(struct device *dev, struct device_attribute= *attr, + char *buf) +{ + enum witrn_attr_channel channel =3D to_sensor_dev_attr(attr)->index; + struct witrn_priv *priv =3D dev_get_drvdata(dev); + struct witrn_sensor sensor; + int ret; + s64 val; + + ret =3D witrn_collect_sensor(priv, &sensor); + if (ret) + return ret; + + switch (channel) { + case ATTR_CHARGE: + ret =3D hwmon_fp_float_to_s64(le32_to_cpu(sensor.record_charge), + WITRN_SCALE_CHARGE, &val); + if (ret) + return ret; + break; + case ATTR_RECORD_GROUP: + /* +1 to match the index displayed on the meter. */ + val =3D sensor.record_group + 1; + break; + case ATTR_RECORD_TIME: + val =3D le32_to_cpu(sensor.record_time); + break; + case ATTR_UPTIME: + val =3D le32_to_cpu(sensor.uptime); + break; + default: + return -EOPNOTSUPP; + } + + return sysfs_emit(buf, "%lld\n", val); +} + +static ssize_t witrn_attr_label_show(struct device *dev, struct device_att= ribute *attr, + char *buf) +{ + enum witrn_attr_channel channel =3D to_sensor_dev_attr(attr)->index; + const char *str; + + switch (channel) { + case ATTR_CHARGE: + str =3D "CBUS"; /* VBUS charge */ + break; + default: + return -EOPNOTSUPP; + } + + return sysfs_emit(buf, "%s\n", str); +} + +static SENSOR_DEVICE_ATTR_RO(charge1_input, witrn_attr, ATTR_CHARGE); +static SENSOR_DEVICE_ATTR_RO(charge1_label, witrn_attr_label, ATTR_CHARGE); +static SENSOR_DEVICE_ATTR_RO(record_group, witrn_attr, ATTR_RECORD_GROUP); +static SENSOR_DEVICE_ATTR_RO(record_time, witrn_attr, ATTR_RECORD_TIME); +static SENSOR_DEVICE_ATTR_RO(uptime, witrn_attr, ATTR_UPTIME); + +static struct attribute *witrn_attrs[] =3D { + &sensor_dev_attr_charge1_input.dev_attr.attr, + &sensor_dev_attr_charge1_label.dev_attr.attr, + &sensor_dev_attr_record_group.dev_attr.attr, + &sensor_dev_attr_record_time.dev_attr.attr, + &sensor_dev_attr_uptime.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(witrn); + static int witrn_probe(struct hid_device *hdev, const struct hid_device_id= *id) { struct device *parent =3D &hdev->dev; @@ -219,6 +632,14 @@ static int witrn_probe(struct hid_device *hdev, const = struct hid_device_id *id) return ret; } =20 + priv->hwmon_dev =3D hwmon_device_register_with_info(parent, DRIVER_NAME, = priv, + &witrn_chip_info, witrn_groups); + if (IS_ERR(priv->hwmon_dev)) { + witrn_close_hid(priv); + hid_hw_stop(hdev); + return PTR_ERR(priv->hwmon_dev); + } + return 0; } =20 @@ -226,6 +647,8 @@ static void witrn_remove(struct hid_device *hdev) { struct witrn_priv *priv =3D hid_get_drvdata(hdev); =20 + hwmon_device_unregister(priv->hwmon_dev); + witrn_close_hid(priv); =20 /* Cancel it after closing HID so that it won't be rescheduled. */ --=20 2.53.0