From nobody Thu Apr 16 22:34:50 2026 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5AF6F38E5DC for ; Wed, 25 Feb 2026 09:29:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772011768; cv=none; b=Gr3wjT7WqlZIuH8by01fnbVGLHiSxWJhzwPYJ1LtTnbCSkyMNNWY+5xaHGtxAT1oXdBopsVFSHuMbeQYXtkw4tngwlpaxGWUsqnHz3QsRfZgO8nRIjc3SzU9fPntw/Vp+ylvcgJaW6yiwAYmQvAEHXNDFSC3sbXR4dXSXb/H6l8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772011768; c=relaxed/simple; bh=nfhF+uSMnIk48frjPAZKn3oDK6TMKtAu7341kKYbV4k=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=QujoVRTu/t2NNd5RmXKXGQu0lQ1yhTjBYJfKWKDZ40re3McDYgmjBGQ8ij6Tx5cBgpBH9tUESjXaGwhXQBQ012wHMZlo7wfty1ZzK+ORuPPUBvhoIOlR3Bt22ASUYtk1yi+Ja2nREBAbII0P26OyhS3CV72cnP6TbgZ0eIziMv0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=flipper.net; spf=pass smtp.mailfrom=flipper.net; dkim=pass (2048-bit key) header.d=flipper.net header.i=@flipper.net header.b=MJT9CozY; arc=none smtp.client-ip=209.85.128.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=flipper.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flipper.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=flipper.net header.i=@flipper.net header.b="MJT9CozY" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-4836e3288cdso4049055e9.0 for ; Wed, 25 Feb 2026 01:29:27 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1772011765; x=1772616565; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=IBu7E7R6aJl6XDY9NrZLFpAyzlylvs3/JCsOIqQyDUI=; b=MJT9CozYFMVlySd1Wc04ZN630MVFRGpXiUxXCUrLJ1FYMHAfamTS2f4kaHtKUDVF90 XXDxW0T+7oK4zWltDhDHHA/a4IE8U8XrgaQcwlIAu9mF+dHGmrrix/0Nt6m2MCfNHUsA 0GmMbc/8RVdwZFXFEttktD1JHHtp+3gd8h/JGTL0HBNmtPCrzhazCy7IRoX12Clds1P5 a5xAGtyv3s6nnTMPwNzzHzLCDKz+EIp8pDsZmDqwgfQlg/nw5phBKZ5YtPnCc0swu0oh o3e3+JITX8MFJ8+z30G/cXIHqCSEY/GSV6cTd8WzMkhBs/+3Q6lGUyP4XV/W9kpm3f2R SeJA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772011765; x=1772616565; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=IBu7E7R6aJl6XDY9NrZLFpAyzlylvs3/JCsOIqQyDUI=; b=YZ7jZQRMZ4W8PSKNiiI+F3MOCO0ZnG5XJi3mhInLERADfgumpTRIg2m2dbIzGVEPHY BP1iQOYnm7VO55V/evPU7Q03UjmzQuEbWr1uWbghJFSZW/VZutgVafkW9O18w76kuu6Y iYnQ9nHU7WiOif5wUNJDju4JwzS2vf5PC+w4cBlbUVmHkc1q0H/1eRWh8kId9WYJD9Qp IEUyUwp8jrmHSHrSkSrptKhhhimRjWQCXBe5SRnCAyWIAYRH3sGbyYSbHQ8o+DArmsK9 qbOeM0Ib7LZwhbw4q9LfPE6c9n3Q0J66k1FLlRRLJuJtpPUDHHM8Za4Og6P4qa5azpuW wqJA== X-Forwarded-Encrypted: i=1; AJvYcCXsyuSsqcwXg/Adcc3zzrmwFq2p/IajvHt1bw23UVrntd8Yie074zyEcIGbpUTW1J0FhUh9P9+KIxcrPTU=@vger.kernel.org X-Gm-Message-State: AOJu0YwVlZlBU77o6jea3fcQKbvVKFe4tlM0Gb8nkIZ4HKdNWO1IBRR8 xcqwJgILNPoxN2shhwCndasqXJESHBRjINQsX9BbVWUk1y4XejKY27wA5PHQ4a0j6S0+ME59FPB +8kJP X-Gm-Gg: ATEYQzyQKYIUbcRemQW6vCR+lLrbrmQTAu5Q+Jdob2a4Hs+1Ss0GNsNCBU/6F84dCLX +LTYlsFk8unuu3SkWNC/vMj7f/0DmeFai3LfuGl8pHe9640JXHeCTFVc6HfutzHxsOuC6Gg/szn 0y4jfpI1XK4RrKUpBA8P4Icxm1OJ7o2F/4HldMCfheqYj03iyf4SW8J9mi+yhwNvD3PfHQmstlY 9oJaTymgrPDNYJCk3SjjTxh+LX7Yq3phmQzXBdgFgFdscbgtNf5duav/AKdVxqwjv+TWOPR1Th+ qMDme1La8RkovpYMxj1tP4xXZm4VcLVAK7Zo1bXjr80dSW/1taaMEktP7u1bAGSRJE4RnFqBbGP t9WDjQ4qevRjxGEeDlcSFEYKRxgLWYxNhUZZpMdU8cZxkk/M/3zQJMzglmpX8Z/IVkHurMLT024 FoCzvNNL8QEK4DLgZZJ3l1Ai5pKDxEoyO3vyA5dK6XbUYQZaliik/uhK1icKi/mPUHIKiqTuPrM pk= X-Received: by 2002:a05:600c:3652:b0:477:991c:a17c with SMTP id 5b1f17b1804b1-483bd726fc9mr36024395e9.6.1772011765528; Wed, 25 Feb 2026 01:29:25 -0800 (PST) Received: from alchark-surface.localdomain (bba-94-59-44-101.alshamil.net.ae. [94.59.44.101]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43970d54c5csm33027463f8f.38.2026.02.25.01.29.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Feb 2026 01:29:25 -0800 (PST) From: Alexey Charkov Date: Wed, 25 Feb 2026 13:29:11 +0400 Subject: [PATCH 1/2] dt-bindings: hwmon: Add DT schema for TI INA4230 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: <20260225-ina4230-v1-1-92b1de981d46@flipper.net> References: <20260225-ina4230-v1-0-92b1de981d46@flipper.net> In-Reply-To: <20260225-ina4230-v1-0-92b1de981d46@flipper.net> To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Alexey Charkov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4435; i=alchark@flipper.net; h=from:subject:message-id; bh=nfhF+uSMnIk48frjPAZKn3oDK6TMKtAu7341kKYbV4k=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWTOO/Cl63XXz/DexzzsokIzr/2c3njl55ksWQ7zitDwZ ys+2lV+6ZjIwiDGxWAppsgy99sS26lGfLN2eXh8hZnDygQyRFqkgQEIWBj4chPzSo10jPRMtQ31 DA11jHWMGLg4BWCq2Q8yMkz2+b3EznnicpMDHkz35Ceu0+fp3DmnnnPR6Xuc1cGfrGYwMnyf1VJ cdDBm65K4G8xf+M+LPtxu0rf3+635qU6iVn+ZpjEAAA== X-Developer-Key: i=alchark@flipper.net; a=openpgp; fpr=9DF6A43D95320E9ABA4848F5B2A2D88F1059D4A5 Add DT binding for TI INA4230, which is a 48V 4-channel 16-bit I2C-based current/voltage/power/energy monitor with alert function. Link: https://www.ti.com/product/INA4230 Signed-off-by: Alexey Charkov Reviewed-by: Krzysztof Kozlowski --- .../devicetree/bindings/hwmon/ti,ina4230.yaml | 128 +++++++++++++++++= ++++ 1 file changed, 128 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml b/Docu= mentation/devicetree/bindings/hwmon/ti,ina4230.yaml new file mode 100644 index 000000000000..8027eb902f7e --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/ti,ina4230.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments INA4230 quad-channel power monitors + +maintainers: + - Alexey Charkov + +description: | + The INA4230 is a 48V quad-channel 16-bit current, voltage, power and ene= rgy + monitor with an I2C interface. + + Datasheet: + https://www.ti.com/product/INA4230 + +properties: + compatible: + enum: + - ti,ina4230 + + reg: + maxItems: 1 + + "#address-cells": + description: Required only if a child node is present. + const: 1 + + "#size-cells": + description: Required only if a child node is present. + const: 0 + + vs-supply: + description: phandle to the regulator that provides the VS supply typi= cally + in range from 1.7 V to 5.5 V. + + ti,alert-polarity-active-high: + description: Alert pin is asserted based on the value of Alert polarit= y Bit + of the CONFIG2 register. Default value is 0, for which the alert pin + toggles from high to low during faults. When this property is set, t= he + corresponding register bit is set to 1, and the alert pin toggles fr= om + low to high during faults. + $ref: /schemas/types.yaml#/definitions/flag + +patternProperties: + "^input@[0-3]$": + description: The node contains optional child nodes for four channels. + Each child node describes the information of input source. Input cha= nnels + default to enabled in the chip. Unless channels are explicitly disab= led + in device-tree, input channels will be enabled. + type: object + additionalProperties: false + properties: + reg: + description: Must be 0, 1, 2 or 3, corresponding to the IN1, IN2, = IN3 + or IN4 ports of the INA4230, respectively. + enum: [ 0, 1, 2, 3 ] + + label: + description: name of the input source + + shunt-resistor-micro-ohms: + description: shunt resistor value in micro-Ohm + + ti,maximum-expected-current-microamp: + description: | + This value indicates the maximum current in microamps that you c= an + expect to measure with ina4230 in your circuit. + + This value will be used to calculate the Current_LSB and current= /power + coefficient for the pmbus and to calibrate the IC. + minimum: 32768 + maximum: 4294967295 + default: 32768000 + + required: + - reg + +required: + - compatible + - reg + +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + power-sensor@44 { + compatible =3D "ti,ina4230"; + reg =3D <0x44>; + vs-supply =3D <&vdd_3v0>; + ti,alert-polarity-active-high; + #address-cells =3D <1>; + #size-cells =3D <0>; + + input@0 { + reg =3D <0x0>; + /* + * Input channels are enabled by default in the device and= so + * to disable, must be explicitly disabled in device-tree. + */ + status =3D "disabled"; + }; + + input@1 { + reg =3D <0x1>; + shunt-resistor-micro-ohms =3D <5000>; + }; + + input@2 { + reg =3D <0x2>; + label =3D "VDD_5V"; + shunt-resistor-micro-ohms =3D <5000>; + }; + + input@3 { + reg =3D <0x3>; + }; + }; + }; --=20 2.52.0 From nobody Thu Apr 16 22:34:50 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7EFB938F227 for ; Wed, 25 Feb 2026 09:29:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772011772; cv=none; b=Hl3i11ljpwGCg1R/ZyvaZ+4gkG/LcVl9wVoLuzJnyG10E9nhx+7pGLNEgH6WWzATNcDiNM+w+lcL7P8ikePM+ybxFyzqosJg0PMCHVKjcA5+tv01FO3CgGakSMCjNStoAEy0Yd9wm+v4+U7mKrivkBPDz7/EEIwMgHnGYrjlpHs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772011772; c=relaxed/simple; bh=tu7xQMA4dS6z2r+3nN4/hiLKzpcOIn0XTxTadXxyItY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mh6aWGOic2LZuvzn5R3FENK30yWA9Ytd1OqM5v8v24q5eHAlWOfE1WIglRBvUftvqkPlAKPnQsPY5SZrV11+JvW6roSrUBhkwQHQOMyPEpu0hHNqzJY3FsHffth6Nxw+pn2hRB8MBXrtr3vRisurZxFeXIOj/oNiM7njCserqGQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=flipper.net; spf=pass smtp.mailfrom=flipper.net; dkim=pass (2048-bit key) header.d=flipper.net header.i=@flipper.net header.b=SXW4IlgN; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=flipper.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flipper.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=flipper.net header.i=@flipper.net header.b="SXW4IlgN" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-4806ce0f97bso51944495e9.0 for ; Wed, 25 Feb 2026 01:29:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1772011768; x=1772616568; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=QM5hYChobVj/V1yVUMRjTpqnOwCviXlqkrHD8dDJ6O8=; b=SXW4IlgNwLtKDoWW6Y45qcsD6Leeo+Ci2XByznfk2bOfu6o1fPedWtF683TrOeLMT4 HoeCS53EIE37RUFuNtWYIhbY9OZo5De9C/ZcBbrqBy8CvubS1HqkkRAS0oMjOZ6ZLz/+ k8ApYGr+pTMWzD+n1jhmBCn8VwjhupIPrl5N2tCrYGAWN/0YKZNY3V5oBkricrBJJjnX BYc5QNLnZWNPXhP10tLgCiKWimp2m/HrkpNg94wP1Z0HE+JBaLt6YYCgQD6sB0UrEkLl LoZWRVXIEkP0pcW9ysmc8oOGS5V0Cc8GeojSxrs7mEvOdO/KcutDueTQ0lgPDbML400g JkOQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772011768; x=1772616568; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=QM5hYChobVj/V1yVUMRjTpqnOwCviXlqkrHD8dDJ6O8=; b=OjjEmupzXXZuoSHbCxxQxz74nLX/b9bUGY7wsMPoXQ0J+N2nZ5C6Y8k6DKarUXMtUV MVHNUMq+etUt0S0kpyOGRipWAAzt6vWGgNM9W//BQzQnCbaOHpY7GgG5AtKXRsq2qcIQ vt5YTkpfiFrE57CfTt6xRMWNeI4wbZ6naOtYHSfqGX+vr7c3XZLJ9avUPMbG6bSuIQI9 qxSeKMgpXIrcpAkHcLWf4vhlZEhqVzpuJdfXdCMv6KT7ZRtzzLN/9CYQRJqVDa6CGVAB H0WqntMpl8RMsASGbM35/DgLTjoeY8gwyHaT/7nyuRISqJ27QLfJTG7H85IeNBufke7Q 9HZA== X-Forwarded-Encrypted: i=1; AJvYcCVLGUm2vQpP6Wc4Pv64+qB1n3ZlUHWzZC+O8JzK/4dcfR6tLOi99RzSp+R38Xv9kD2IioLrOV2BktM1IDE=@vger.kernel.org X-Gm-Message-State: AOJu0YxRxMIoja67doxS+EHfMezbwv2cVQl2Vt+Xa3n8wwsfRTo6z3qX 7tGiTdQ4lR1X99iNEYnzLe7CdXfkHwXa5bbDVAZlI7nb8SvxQpweyM0sl0MravCFLJn6fF1QYEm FvaQP X-Gm-Gg: ATEYQzz6wGVyizHWWgf3edcX1E5C+K9+XoXogq7m6BrJKUh4VJyfj8ZALVFhyNZc38M mvI3hYtQ+8nO9pP6aprNPxOWi+njvGGfL69FxSfG4pPU4TmtZv8SP2csDCu4zjC9wTBsn3ENgXh YxUc2c/w1fIZXCjFIlqAbY6UItTQUyNK6zv5te90fe5di887YwyExAxxD2Xhfi/fBvPrlOG0B6K H6tZi3dFhtLLslWDeURGRDMFpZnRSlwM7YVKev+Bx9hxKkcJrE6w2rjkDXuxPXGSBrbt98XAQLe VUCwUVSDW8I6O5DV2nwCUIi/dyqKY6v2Ssc3pPn07EklsQBNfA0VoH6X2aV15rnHAR+un8UbKZC x5nV1+k6q5OrP9BZ/QiGFzjLTNvR5I9DXmu2ZnDHyghozv3ME9lddMkc1g7SE6gNWRgnVpAGwX+ dkggf39HKeJ7jjeiXjrUGl2WESxYLN64bEtmuGIf86TfTvnF14xDEDxLIF9NHbhelSklpnQh8We nNuUFUaFmK65A== X-Received: by 2002:a05:600c:190f:b0:477:9a28:b0a4 with SMTP id 5b1f17b1804b1-483a95574c5mr254132405e9.0.1772011767603; Wed, 25 Feb 2026 01:29:27 -0800 (PST) Received: from alchark-surface.localdomain (bba-94-59-44-101.alshamil.net.ae. [94.59.44.101]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43970d54c5csm33027463f8f.38.2026.02.25.01.29.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 25 Feb 2026 01:29:27 -0800 (PST) From: Alexey Charkov Date: Wed, 25 Feb 2026 13:29:12 +0400 Subject: [PATCH 2/2] hwmon: Add support for TI INA4230 power monitor 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: <20260225-ina4230-v1-2-92b1de981d46@flipper.net> References: <20260225-ina4230-v1-0-92b1de981d46@flipper.net> In-Reply-To: <20260225-ina4230-v1-0-92b1de981d46@flipper.net> To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Alexey Charkov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=32401; i=alchark@flipper.net; h=from:subject:message-id; bh=tu7xQMA4dS6z2r+3nN4/hiLKzpcOIn0XTxTadXxyItY=; b=kA0DAAoWsqLYjxBZ1KUByyZiAGmewPTIDAUyomvaUiVOwDQlnsnhEwKGvEcgbIIHsI1A3fIjW oiRBAAWCgA5FiEEnfakPZUyDpq6SEj1sqLYjxBZ1KUFAmmewPQbFIAAAAAABAAObWFudTIsMi41 KzEuMTEsMywyAAoJELKi2I8QWdSliT8BAPJCrHvWGqtyolF8+UM5tFkUlRylxlUXA2OZfs+GYe4 ZAP49p6Ufq28wHhzn51T3797JjR/6JTKZ2/RX9AxBQoOKAA== X-Developer-Key: i=alchark@flipper.net; a=openpgp; fpr=9DF6A43D95320E9ABA4848F5B2A2D88F1059D4A5 Add a driver for the TI INA4230, a 4-channel power monitor with I2C interface. The driver supports voltage, current, power and energy measurements, but skips the alert functionality in this initial implementation. Signed-off-by: Alexey Charkov --- MAINTAINERS | 6 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ina4230.c | 997 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 1015 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4d879f6a7b51..77f7a416e682 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12511,6 +12511,12 @@ S: Maintained F: Documentation/hwmon/ina233.rst F: drivers/hwmon/pmbus/ina233.c =20 +INA4230 HWMON DRIVER +M: Alexey Charkov +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/ina4230.c + INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2c058ebf70ae..080888c9eb44 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2291,6 +2291,17 @@ config SENSORS_INA3221 This driver can also be built as a module. If so, the module will be called ina3221. =20 +config SENSORS_INA4230 + tristate "Texas Instruments INA4230 Quad Current/Voltage Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TI INA4230 Quad + Current/Voltage Monitor. + + This driver can also be built as a module. If so, the module + will be called ina4230. + config SENSORS_SPD5118 tristate "SPD5118 Compliant Temperature Sensors" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9d3659123ad5..52722d8a33d9 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_SENSORS_INA209) +=3D ina209.o obj-$(CONFIG_SENSORS_INA2XX) +=3D ina2xx.o obj-$(CONFIG_SENSORS_INA238) +=3D ina238.o obj-$(CONFIG_SENSORS_INA3221) +=3D ina3221.o +obj-$(CONFIG_SENSORS_INA4230) +=3D ina4230.o obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) +=3D intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_ISL28022) +=3D isl28022.o obj-$(CONFIG_SENSORS_IT87) +=3D it87.o diff --git a/drivers/hwmon/ina4230.c b/drivers/hwmon/ina4230.c new file mode 100644 index 000000000000..fae104b4c526 --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,997 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * INA4230 Quad Current/Voltage Monitor + * + * Based on INA3221 driver by Texas Instruments Incorporated - https://www= .ti.com/ + * Adapted for INA4230 by Alexey Charkov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INA4230_DRIVER_NAME "ina4230" + +#define INA4230_SHUNT_VOLTAGE_CH1 0x00 +#define INA4230_BUS_VOLTAGE_CH1 0x01 +#define INA4230_CURRENT_CH1 0x02 +#define INA4230_POWER_CH1 0x03 +#define INA4230_ENERGY_CH1 0x04 +#define INA4230_CALIBRATION_CH1 0x05 +#define INA4230_ALERT_LIMIT1 0x06 +#define INA4230_ALERT_CONFIG1 0x07 +#define INA4230_SHUNT_VOLTAGE_CH2 0x08 +#define INA4230_BUS_VOLTAGE_CH2 0x09 +#define INA4230_CURRENT_CH2 0x0A +#define INA4230_POWER_CH2 0x0B +#define INA4230_ENERGY_CH2 0x0C +#define INA4230_CALIBRATION_CH2 0x0D +#define INA4230_ALERT_LIMIT2 0x0E +#define INA4230_ALERT_CONFIG2 0x0F +#define INA4230_SHUNT_VOLTAGE_CH3 0x10 +#define INA4230_BUS_VOLTAGE_CH3 0x11 +#define INA4230_CURRENT_CH3 0x12 +#define INA4230_POWER_CH3 0x13 +#define INA4230_ENERGY_CH3 0x14 +#define INA4230_CALIBRATION_CH3 0x15 +#define INA4230_ALERT_LIMIT3 0x16 +#define INA4230_ALERT_CONFIG3 0x17 +#define INA4230_SHUNT_VOLTAGE_CH4 0x18 +#define INA4230_BUS_VOLTAGE_CH4 0x19 +#define INA4230_CURRENT_CH4 0x1A +#define INA4230_POWER_CH4 0x1B +#define INA4230_ENERGY_CH4 0x1C +#define INA4230_CALIBRATION_CH4 0x1D +#define INA4230_ALERT_LIMIT4 0x1E +#define INA4230_ALERT_CONFIG4 0x1F +#define INA4230_CONFIG1 0x20 +#define INA4230_CONFIG2 0x21 +#define INA4230_FLAGS 0x22 +#define INA4230_MANUFACTURER_ID 0x7E + +#define INA4230_CALIBRATION_MASK GENMASK(14, 0) + +#define INA4230_ALERT_CHANNEL_MASK GENMASK(4, 3) +#define INA4230_ALERT_MASK GENMASK(2, 0) +/* Shunt voltage over limit */ +#define INA4230_ALERT_MASK_SOL 0x1 +/* Shunt voltage under limit */ +#define INA4230_ALERT_MASK_SUL 0x2 +/* Bus voltage over limit */ +#define INA4230_ALERT_MASK_BOL 0x3 +/* Bus voltage under limit */ +#define INA4230_ALERT_MASK_BUL 0x4 +/* Power over limit */ +#define INA4230_ALERT_MASK_POL 0x5 + +#define INA4230_CONFIG1_ACTIVE_CHANNEL_MASK GENMASK(15, 12) +#define INA4230_CONFIG1_AVG_MASK GENMASK(11, 9) +#define INA4230_CONFIG1_VBUSCT_MASK GENMASK(8, 6) +#define INA4230_CONFIG1_VSHCT_MASK GENMASK(5, 3) +#define INA4230_CONFIG1_MODE_MASK GENMASK(2, 0) +#define INA4230_MODE_POWERDOWN 0 +#define INA4230_MODE_SHUNT_SINGLE 1 +#define INA4230_MODE_BUS_SINGLE 2 +#define INA4230_MODE_BUS_SHUNT_SINGLE 3 +#define INA4230_MODE_POWERDOWN1 4 +#define INA4230_MODE_SHUNT_CONTINUOUS 5 +#define INA4230_MODE_BUS_CONTINUOUS 6 +#define INA4230_MODE_BUS_SHUNT_CONTINUOUS 7 + +#define INA4230_CONFIG2_RST BIT(15) +#define INA4230_CONFIG2_ACC_RST_MASK GENMASK(11, 8) +#define INA4230_CONFIG2_CNVR_MASK BIT(7) +#define INA4230_CONFIG2_ENOF_MASK BIT(6) +#define INA4230_CONFIG2_ALERT_LATCH BIT(5) +#define INA4230_CONFIG2_ALERT_POL BIT(4) +#define INA4230_CONFIG2_RANGE_MASK GENMASK(3, 0) +#define INA4230_CONFIG2_RANGE_CH(x) \ + FIELD_PREP(INA4230_CONFIG2_RANGE_MASK, BIT((x))) + +#define INA4230_FLAGS_LIMIT4_ALERT BIT(15) +#define INA4230_FLAGS_LIMIT3_ALERT BIT(14) +#define INA4230_FLAGS_LIMIT2_ALERT BIT(13) +#define INA4230_FLAGS_LIMIT1_ALERT BIT(12) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH4 BIT(11) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH3 BIT(10) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH2 BIT(9) +#define INA4230_FLAGS_ENERGY_OVERFLOW_CH1 BIT(8) +#define INA4230_FLAGS_CVRF BIT(7) +#define INA4230_FLAGS_MATH_OVERFLOW BIT(6) + +#define INA4230_RSHUNT_DEFAULT 10000 +#define INA4230_CONFIG_DEFAULT \ + (FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, 0xF) | \ + FIELD_PREP(INA4230_CONFIG1_AVG_MASK, 0x1) | \ + FIELD_PREP(INA4230_CONFIG1_VBUSCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_VSHCT_MASK, 0x4) | \ + FIELD_PREP(INA4230_CONFIG1_MODE_MASK, 0x7)) +#define INA4230_CONFIG_CHx_EN(x) \ + FIELD_PREP(INA4230_CONFIG1_ACTIVE_CHANNEL_MASK, BIT((x))) + +enum ina4230_fields { + /* Alert configuration settings: channel masks */ + F_ALERT1_CH, F_ALERT2_CH, F_ALERT3_CH, F_ALERT4_CH, + /* Alert configuration settings: alert masks */ + F_ALERT1_TYPE, F_ALERT2_TYPE, F_ALERT3_TYPE, F_ALERT4_TYPE, + /* Configuration registers */ + F_CH_EN, F_AVG, F_VBUSCT, F_VSHCT, F_MODE, + F_RST, F_ACC_RST, F_CNV_ALERT, F_ENOF, F_ALERT_LATCH, F_ALERT_POL, F_RANG= E, + /* Status flags */ + F_LIMIT1_ALERT, F_LIMIT2_ALERT, F_LIMIT3_ALERT, F_LIMIT4_ALERT, + F_ENERGY_OVERFLOW_CH1, F_ENERGY_OVERFLOW_CH2, F_ENERGY_OVERFLOW_CH3, F_EN= ERGY_OVERFLOW_CH4, + F_CVRF, F_MATH_OVERFLOW, + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field ina4230_reg_fields[] =3D { + [F_ALERT1_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG1, 3, 4), + [F_ALERT2_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG2, 3, 4), + [F_ALERT3_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG3, 3, 4), + [F_ALERT4_CH] =3D REG_FIELD(INA4230_ALERT_CONFIG4, 3, 4), + + [F_ALERT1_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG1, 0, 2), + [F_ALERT2_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG2, 0, 2), + [F_ALERT3_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG3, 0, 2), + [F_ALERT4_TYPE] =3D REG_FIELD(INA4230_ALERT_CONFIG4, 0, 2), + + [F_CH_EN] =3D REG_FIELD(INA4230_CONFIG1, 12, 15), + [F_AVG] =3D REG_FIELD(INA4230_CONFIG1, 9, 11), + [F_VBUSCT] =3D REG_FIELD(INA4230_CONFIG1, 6, 8), + [F_VSHCT] =3D REG_FIELD(INA4230_CONFIG1, 3, 5), + [F_MODE] =3D REG_FIELD(INA4230_CONFIG1, 0, 2), + [F_RST] =3D REG_FIELD(INA4230_CONFIG2, 15, 15), + [F_ACC_RST] =3D REG_FIELD(INA4230_CONFIG2, 8, 11), + [F_CNV_ALERT] =3D REG_FIELD(INA4230_CONFIG2, 7, 7), + [F_ENOF] =3D REG_FIELD(INA4230_CONFIG2, 6, 6), + [F_ALERT_LATCH] =3D REG_FIELD(INA4230_CONFIG2, 5, 5), + [F_ALERT_POL] =3D REG_FIELD(INA4230_CONFIG2, 4, 4), + [F_RANGE] =3D REG_FIELD(INA4230_CONFIG2, 0, 3), + + [F_LIMIT1_ALERT] =3D REG_FIELD(INA4230_FLAGS, 12, 12), + [F_LIMIT2_ALERT] =3D REG_FIELD(INA4230_FLAGS, 13, 13), + [F_LIMIT3_ALERT] =3D REG_FIELD(INA4230_FLAGS, 14, 14), + [F_LIMIT4_ALERT] =3D REG_FIELD(INA4230_FLAGS, 15, 15), + [F_ENERGY_OVERFLOW_CH1] =3D REG_FIELD(INA4230_FLAGS, 8, 8), + [F_ENERGY_OVERFLOW_CH2] =3D REG_FIELD(INA4230_FLAGS, 9, 9), + [F_ENERGY_OVERFLOW_CH3] =3D REG_FIELD(INA4230_FLAGS, 10, 10), + [F_ENERGY_OVERFLOW_CH4] =3D REG_FIELD(INA4230_FLAGS, 11, 11), + [F_CVRF] =3D REG_FIELD(INA4230_FLAGS, 7, 7), + [F_MATH_OVERFLOW] =3D REG_FIELD(INA4230_FLAGS, 6, 6), +}; + +enum ina4230_channels { + INA4230_CHANNEL1, + INA4230_CHANNEL2, + INA4230_CHANNEL3, + INA4230_CHANNEL4, + INA4230_NUM_CHANNELS +}; + +/** + * struct ina4230_input - channel input source specific information + * @label: label of channel input source + * @shunt_resistor: shunt resistor value of channel input source + * @shunt_gain: gain of shunt voltage for current calculation + * @max_expected_current: maximum expected current in micro-Ampere for ADC + * calibration + * @current_lsb_uA: current LSB in micro-Amperes + * @disconnected: connection status of channel input source + */ +struct ina4230_input { + const char *label; + int shunt_resistor; + int shunt_gain; + int max_expected_current; + int current_lsb_uA; + bool disconnected; +}; + +/** + * struct ina4230_data - device specific information + * @pm_dev: Device pointer for pm runtime + * @regmap: Register map of the device + * @fields: Register fields of the device + * @inputs: Array of channel input source specific structures + * @reg_config1: cached value of CONFIG1 register + * @reg_config2: cached value of CONFIG2 register + * @single_shot: flag indicating single-shot measurement mode + * @alert_active_high: flag indicating alert polarity is active high + */ +struct ina4230_data { + struct device *pm_dev; + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct ina4230_input inputs[INA4230_NUM_CHANNELS]; + unsigned int reg_config1; + unsigned int reg_config2; + bool single_shot; + bool alert_active_high; +}; + +static inline bool ina4230_is_enabled(struct ina4230_data *ina, int channe= l) +{ + return pm_runtime_active(ina->pm_dev) && + !ina->inputs[channel].disconnected; +} + +/* Lookup table for Bus and Shunt conversion times in usec */ +static const u16 ina4230_conv_time[] =3D { + 140, 204, 332, 588, 1100, 2116, 4156, 8244, +}; + +/* Lookup table for number of samples used in averaging mode */ +static const int ina4230_avg_samples[] =3D { + 1, 4, 16, 64, 128, 256, 512, 1024, +}; + +/* Converting update_interval in msec to conversion time in usec */ +static inline u32 ina4230_interval_ms_to_conv_time(u16 config, int interva= l) +{ + u32 channels =3D hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 samples_idx =3D FIELD_GET(INA4230_CONFIG1_AVG_MASK, config); + u32 samples =3D ina4230_avg_samples[samples_idx]; + + /* Bisect the result to Bus and Shunt conversion times */ + return DIV_ROUND_CLOSEST(interval * 1000 / 2, channels * samples); +} + +/* Converting CONFIG register value to update_interval in usec */ +static inline u32 ina4230_reg_to_interval_us(u16 config) +{ + u32 channels =3D hweight16(config & INA4230_CONFIG1_ACTIVE_CHANNEL_MASK); + u32 vbus_ct_idx =3D FIELD_GET(INA4230_CONFIG1_VBUSCT_MASK, config); + u32 vsh_ct_idx =3D FIELD_GET(INA4230_CONFIG1_VSHCT_MASK, config); + u32 vbus_ct =3D ina4230_conv_time[vbus_ct_idx]; + u32 vsh_ct =3D ina4230_conv_time[vsh_ct_idx]; + + /* Calculate total conversion time */ + return channels * (vbus_ct + vsh_ct); +} + +static inline int ina4230_wait_for_data(struct ina4230_data *ina) +{ + u32 wait, cvrf; + + wait =3D ina4230_reg_to_interval_us(ina->reg_config1); + + /* Polling the CVRF bit to make sure read data is ready */ + return regmap_field_read_poll_timeout(ina->fields[F_CVRF], + cvrf, cvrf, wait, wait * 2); +} + +static const u8 ina4230_calibration_reg[] =3D { + INA4230_CALIBRATION_CH1, + INA4230_CALIBRATION_CH2, + INA4230_CALIBRATION_CH3, + INA4230_CALIBRATION_CH4, +}; + +static int ina4230_set_calibration(struct ina4230_data *ina, int channel) +{ + struct ina4230_input *input =3D &ina->inputs[channel]; + u8 reg =3D ina4230_calibration_reg[channel]; + int shunt_range_uV, ret; + u32 calibration; + + shunt_range_uV =3D mult_frac(input->max_expected_current, + input->shunt_resistor, + 1000000); + input->shunt_gain =3D shunt_range_uV > 20480 ? 1 : 4; + ina->reg_config2 &=3D ~INA4230_CONFIG2_RANGE_CH(channel); + if (input->shunt_gain =3D=3D 4) + ina->reg_config2 |=3D INA4230_CONFIG2_RANGE_CH(channel); + + ret =3D regmap_write(ina->regmap, INA4230_CONFIG2, ina->reg_config2); + if (ret) + return ret; + + input->current_lsb_uA =3D DIV_ROUND_UP(input->max_expected_current, 32768= ); + calibration =3D DIV_ROUND_CLOSEST(5120000000ULL, + (u64)input->current_lsb_uA * + input->shunt_resistor * + input->shunt_gain); + + return regmap_write(ina->regmap, reg, calibration); +} + +static const u8 ina4230_in_reg[] =3D { + INA4230_BUS_VOLTAGE_CH1, + INA4230_BUS_VOLTAGE_CH2, + INA4230_BUS_VOLTAGE_CH3, + INA4230_BUS_VOLTAGE_CH4, + INA4230_SHUNT_VOLTAGE_CH1, + INA4230_SHUNT_VOLTAGE_CH2, + INA4230_SHUNT_VOLTAGE_CH3, + INA4230_SHUNT_VOLTAGE_CH4, +}; + +static const u8 ina4230_curr_reg[][INA4230_NUM_CHANNELS] =3D { + [hwmon_curr_input] =3D { INA4230_CURRENT_CH1, INA4230_CURRENT_CH2, + INA4230_CURRENT_CH3, INA4230_CURRENT_CH4 }, +}; + +static const u8 ina4230_power_reg[] =3D { + INA4230_POWER_CH1, INA4230_POWER_CH2, INA4230_POWER_CH3, INA4230_POWER_CH4 +}; + +static const u8 ina4230_energy_reg[] =3D { + INA4230_ENERGY_CH1, INA4230_ENERGY_CH2, + INA4230_ENERGY_CH3, INA4230_ENERGY_CH4 +}; + +static int ina4230_read_chip(struct device *dev, u32 attr, long *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int regval; + + switch (attr) { + case hwmon_chip_samples: + regval =3D FIELD_GET(INA4230_CONFIG1_AVG_MASK, ina->reg_config1); + *val =3D ina4230_avg_samples[regval]; + return 0; + case hwmon_chip_update_interval: + /* Return in msec */ + *val =3D ina4230_reg_to_interval_us(ina->reg_config1); + *val =3D DIV_ROUND_CLOSEST(*val, 1000); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_in(struct device *dev, u32 attr, int channel, long= *val) +{ + const bool is_shunt =3D channel > INA4230_CHANNEL4; + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_in_reg[channel]; + int regval, ret; + + /* + * Translate shunt channel index to sensor channel index + */ + channel %=3D INA4230_NUM_CHANNELS; + + switch (attr) { + case hwmon_in_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + /* Write CONFIG register to trigger a single-shot measurement */ + if (ina->single_shot) { + regmap_write(ina->regmap, INA4230_CONFIG1, + ina->reg_config1); + + ret =3D ina4230_wait_for_data(ina); + if (ret) + return ret; + } + + ret =3D regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + /* + * Scale of shunt voltage (uV): LSB is 2.5uV or 625nV + * depending on gain setting + * Scale of bus voltage (mV): LSB is 1.6mV + */ + *val =3D (int16_t)regval * + (long)(is_shunt ? 2500 / ina->inputs[channel].shunt_gain + : 1600000) / 1000000; + return 0; + case hwmon_in_enable: + *val =3D ina4230_is_enabled(ina, channel); + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_power(struct device *dev, u32 attr, int channel, l= ong *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_power_reg[channel]; + int regval, ret; + + switch (attr) { + case hwmon_power_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret =3D regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val =3D (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_energy(struct device *dev, u32 attr, int channel, = long *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_energy_reg[channel]; + int ret; + __be32 regval; + + switch (attr) { + case hwmon_energy_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ret =3D regmap_raw_read(ina->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val =3D be32_to_cpu(regval) * + (long)ina->inputs[channel].current_lsb_uA * 32; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read_curr(struct device *dev, u32 attr, + int channel, long *val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u8 reg =3D ina4230_curr_reg[attr][channel]; + int regval, ret; + + switch (attr) { + case hwmon_curr_input: + if (!ina4230_is_enabled(ina, channel)) + return -ENODATA; + + ina4230_set_calibration(ina, channel); + + /* Write CONFIG1 register to trigger a single-shot measurement */ + if (ina->single_shot) { + regmap_write(ina->regmap, INA4230_CONFIG1, + ina->reg_config1); + + ret =3D ina4230_wait_for_data(ina); + if (ret) + return ret; + } + + ret =3D regmap_read(ina->regmap, reg, ®val); + if (ret) + return ret; + + *val =3D (int16_t)regval * + (long)ina->inputs[channel].current_lsb_uA / 1000; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_write_chip(struct device *dev, u32 attr, long val) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int idx; + u32 tmp; + + switch (attr) { + case hwmon_chip_samples: + idx =3D find_closest(val, ina4230_avg_samples, + ARRAY_SIZE(ina4230_avg_samples)); + + FIELD_MODIFY(INA4230_CONFIG1_AVG_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + case hwmon_chip_update_interval: + tmp =3D ina4230_interval_ms_to_conv_time(ina->reg_config1, val); + idx =3D find_closest(tmp, ina4230_conv_time, + ARRAY_SIZE(ina4230_conv_time)); + + FIELD_MODIFY(INA4230_CONFIG1_VBUSCT_MASK, &ina->reg_config1, idx); + FIELD_MODIFY(INA4230_CONFIG1_VSHCT_MASK, &ina->reg_config1, idx); + return regmap_write(ina->regmap, INA4230_CONFIG1, ina->reg_config1); + default: + return -EOPNOTSUPP; + } +} + +static int ina4230_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret =3D ina4230_read_chip(dev, attr, val); + break; + case hwmon_in: + /* 0-align channel ID */ + ret =3D ina4230_read_in(dev, attr, channel - 1, val); + break; + case hwmon_curr: + ret =3D ina4230_read_curr(dev, attr, channel, val); + break; + case hwmon_power: + ret =3D ina4230_read_power(dev, attr, channel, val); + break; + case hwmon_energy: + ret =3D ina4230_read_energy(dev, attr, channel, val); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_chip: + ret =3D ina4230_write_chip(dev, attr, val); + break; + default: + ret =3D -EOPNOTSUPP; + break; + } + return ret; +} + +static int ina4230_read_string(struct device *dev, enum hwmon_sensor_types= type, + u32 attr, int channel, const char **str) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int index =3D channel - 1; + + *str =3D ina->inputs[index].label; + + return 0; +} + +static umode_t ina4230_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ina4230_data *ina =3D drvdata; + const struct ina4230_input *input =3D NULL; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + case hwmon_chip_update_interval: + return 0644; + default: + return 0; + } + case hwmon_in: + /* Ignore in0_ */ + if (channel =3D=3D 0) + return 0; + + switch (attr) { + case hwmon_in_label: + if (channel - 1 <=3D INA4230_CHANNEL4) + input =3D &ina->inputs[channel - 1]; + /* Hide label node if label is not provided */ + return (input && input->label) ? 0444 : 0; + case hwmon_in_input: + return 0444; + case hwmon_in_enable: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return 0444; + default: + return 0; + } + case hwmon_energy: + switch (attr) { + case hwmon_energy_input: + return 0444; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_channel_info * const ina4230_info[] =3D { + HWMON_CHANNEL_INFO(chip, + HWMON_C_SAMPLES, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(in, + /* 0: dummy, skipped in is_visible */ + HWMON_I_INPUT, + /* 1-4: input voltage Channels */ + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + /* 5-8: shunt voltage Channels */ + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT), + HWMON_CHANNEL_INFO(curr, + /* 1-4: current channels*/ + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT, + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 1-4: power channels*/ + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT, + HWMON_P_INPUT), + HWMON_CHANNEL_INFO(energy, + /* 1-4: energy channels*/ + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT, + HWMON_E_INPUT), + NULL +}; + +static const struct hwmon_ops ina4230_hwmon_ops =3D { + .is_visible =3D ina4230_is_visible, + .read_string =3D ina4230_read_string, + .read =3D ina4230_read, + .write =3D ina4230_write, +}; + +static const struct hwmon_chip_info ina4230_chip_info =3D { + .ops =3D &ina4230_hwmon_ops, + .info =3D ina4230_info, +}; + +/* Extra attribute groups */ +static ssize_t ina4230_shunt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sd_attr =3D to_sensor_dev_attr(attr); + struct ina4230_data *ina =3D dev_get_drvdata(dev); + unsigned int channel =3D sd_attr->index; + struct ina4230_input *input =3D &ina->inputs[channel]; + + return sysfs_emit(buf, "%d\n", input->shunt_resistor); +} + +static ssize_t ina4230_shunt_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *sd_attr =3D to_sensor_dev_attr(attr); + struct ina4230_data *ina =3D dev_get_drvdata(dev); + unsigned int channel =3D sd_attr->index; + struct ina4230_input *input =3D &ina->inputs[channel]; + int val; + int ret; + + ret =3D kstrtoint(buf, 0, &val); + if (ret) + return ret; + + val =3D clamp_val(val, 1, INT_MAX); + + input->shunt_resistor =3D val; + ret =3D ina4230_set_calibration(ina, channel); + if (ret) + return ret; + + return count; +} + +/* shunt resistance */ +static SENSOR_DEVICE_ATTR_RW(shunt1_resistor, ina4230_shunt, INA4230_CHANN= EL1); +static SENSOR_DEVICE_ATTR_RW(shunt2_resistor, ina4230_shunt, INA4230_CHANN= EL2); +static SENSOR_DEVICE_ATTR_RW(shunt3_resistor, ina4230_shunt, INA4230_CHANN= EL3); +static SENSOR_DEVICE_ATTR_RW(shunt4_resistor, ina4230_shunt, INA4230_CHANN= EL4); + +static struct attribute *ina4230_attrs[] =3D { + &sensor_dev_attr_shunt1_resistor.dev_attr.attr, + &sensor_dev_attr_shunt2_resistor.dev_attr.attr, + &sensor_dev_attr_shunt3_resistor.dev_attr.attr, + &sensor_dev_attr_shunt4_resistor.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ina4230); + +static const struct regmap_range ina4230_vol_ranges[] =3D { + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH1, INA4230_ENERGY_CH1), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH2, INA4230_ENERGY_CH2), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH3, INA4230_ENERGY_CH3), + regmap_reg_range(INA4230_SHUNT_VOLTAGE_CH4, INA4230_ENERGY_CH4), + regmap_reg_range(INA4230_FLAGS, INA4230_FLAGS), +}; + +static const struct regmap_access_table ina4230_volatile_table =3D { + .yes_ranges =3D ina4230_vol_ranges, + .n_yes_ranges =3D ARRAY_SIZE(ina4230_vol_ranges), +}; + +static const struct regmap_config ina4230_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 16, + + .cache_type =3D REGCACHE_MAPLE, + .volatile_table =3D &ina4230_volatile_table, +}; + +static int ina4230_probe_child_from_dt(struct device *dev, + struct device_node *child, + struct ina4230_data *ina) +{ + struct ina4230_input *input; + u32 val; + int ret; + + ret =3D of_property_read_u32(child, "reg", &val); + if (ret) { + dev_err(dev, "missing reg property of %pOFn\n", child); + return ret; + } else if (val > INA4230_CHANNEL4) { + dev_err(dev, "invalid reg %d of %pOFn\n", val, child); + return -EINVAL; + } + + input =3D &ina->inputs[val]; + + /* Log the disconnected channel input */ + if (!of_device_is_available(child)) { + input->disconnected =3D true; + return 0; + } + + /* Save the connected input label if available */ + of_property_read_string(child, "label", &input->label); + + /* Overwrite default shunt resistor value optionally */ + if (!of_property_read_u32(child, "shunt-resistor-micro-ohms", &val)) { + if (val < 1 || val > INT_MAX) { + dev_err(dev, "invalid shunt resistor value %u of %pOFn\n", + val, child); + return -EINVAL; + } + input->shunt_resistor =3D val; + } + + /* Save the expected maxcurrent */ + if (!of_property_read_u32(child, "ti,maximum-expected-current-microamp", = &val)) { + if (val < 32768 || val > INT_MAX) { + dev_err(dev, "invalid max current value %u of %pOFn\n", + val, child); + return -EINVAL; + } + input->max_expected_current =3D val; + } + + return 0; +} + +static int ina4230_probe_from_dt(struct device *dev, struct ina4230_data *= ina) +{ + const struct device_node *np =3D dev->of_node; + int ret; + + /* Compatible with non-DT platforms */ + if (!np) + return 0; + + ina->single_shot =3D of_property_read_bool(np, "ti,single-shot"); + ina->alert_active_high =3D of_property_read_bool(np, "ti,alert-polarity-a= ctive-high"); + + for_each_child_of_node_scoped(np, child) { + ret =3D ina4230_probe_child_from_dt(dev, child, ina); + if (ret) + return ret; + } + + return 0; +} + +static int ina4230_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct ina4230_data *ina; + struct device *hwmon_dev; + int i, ret; + + ina =3D devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL); + if (!ina) + return -ENOMEM; + + ina->regmap =3D devm_regmap_init_i2c(client, &ina4230_regmap_config); + if (IS_ERR(ina->regmap)) { + dev_err(dev, "Unable to allocate register map\n"); + return PTR_ERR(ina->regmap); + } + + for (i =3D 0; i < F_MAX_FIELDS; i++) { + ina->fields[i] =3D devm_regmap_field_alloc(dev, + ina->regmap, + ina4230_reg_fields[i]); + if (IS_ERR(ina->fields[i])) { + dev_err(dev, "Unable to allocate regmap fields\n"); + return PTR_ERR(ina->fields[i]); + } + } + + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + ina->inputs[i].shunt_resistor =3D INA4230_RSHUNT_DEFAULT; + /* Default for 1mA LSB current measurements */ + ina->inputs[i].max_expected_current =3D 32768000; + } + + ret =3D ina4230_probe_from_dt(dev, ina); + if (ret) { + dev_err(dev, "Unable to probe from device tree\n"); + return ret; + } + + /* The driver will be reset, so use reset value */ + ina->reg_config1 =3D INA4230_CONFIG_DEFAULT; + ina->reg_config2 =3D 0; + + if (ina->single_shot) + FIELD_MODIFY(INA4230_CONFIG1_MODE_MASK, &ina->reg_config1, + INA4230_MODE_BUS_SHUNT_SINGLE); + + /* Disable channels if their inputs are disconnected */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + ina->reg_config1 &=3D ~INA4230_CONFIG_CHx_EN(i); + } + + /* Set calibration values */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + ina4230_set_calibration(ina, i); + } + + ina->pm_dev =3D dev; + dev_set_drvdata(dev, ina); + + /* Enable PM runtime -- status is suspended by default */ + pm_runtime_enable(ina->pm_dev); + + /* Initialize (resume) the device */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (ina->inputs[i].disconnected) + continue; + /* Match the refcount with number of enabled channels */ + ret =3D pm_runtime_get_sync(ina->pm_dev); + if (ret < 0) + goto fail; + } + + hwmon_dev =3D devm_hwmon_device_register_with_info(dev, client->name, ina, + &ina4230_chip_info, + ina4230_groups); + if (IS_ERR(hwmon_dev)) { + dev_err(dev, "Unable to register hwmon device\n"); + ret =3D PTR_ERR(hwmon_dev); + goto fail; + } + + return 0; + +fail: + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + /* pm_runtime_put_noidle() will decrease the PM refcount until 0 */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) + pm_runtime_put_noidle(ina->pm_dev); + + return ret; +} + +static void ina4230_remove(struct i2c_client *client) +{ + struct ina4230_data *ina =3D dev_get_drvdata(&client->dev); + int i; + + pm_runtime_disable(ina->pm_dev); + pm_runtime_set_suspended(ina->pm_dev); + + /* pm_runtime_put_noidle() will decrease the PM refcount until 0 */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) + pm_runtime_put_noidle(ina->pm_dev); +} + +static int ina4230_suspend(struct device *dev) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int ret; + + /* Save config register value and enable cache-only */ + ret =3D regmap_read(ina->regmap, INA4230_CONFIG1, &ina->reg_config1); + if (ret) + return ret; + + regcache_cache_only(ina->regmap, true); + regcache_mark_dirty(ina->regmap); + + return 0; +} + +static int ina4230_resume(struct device *dev) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + int ret; + + regcache_cache_only(ina->regmap, false); + + /* Software reset the chip */ + ret =3D regmap_field_write(ina->fields[F_RST], true); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + /* Restore cached register values to hardware */ + ret =3D regcache_sync(ina->regmap); + if (ret) + return ret; + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ina4230_pm, ina4230_suspend, ina4230_resu= me, + NULL); + +static const struct of_device_id ina4230_of_match_table[] =3D { + { .compatible =3D "ti,ina4230", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ina4230_of_match_table); + +static const struct i2c_device_id ina4230_ids[] =3D { + { "ina4230" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, ina4230_ids); + +static struct i2c_driver ina4230_i2c_driver =3D { + .probe =3D ina4230_probe, + .remove =3D ina4230_remove, + .driver =3D { + .name =3D INA4230_DRIVER_NAME, + .of_match_table =3D ina4230_of_match_table, + .pm =3D pm_ptr(&ina4230_pm), + }, + .id_table =3D ina4230_ids, +}; +module_i2c_driver(ina4230_i2c_driver); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("Texas Instruments INA4230 HWMon Driver"); +MODULE_LICENSE("GPL"); --=20 2.52.0