From nobody Thu Apr 9 12:08:13 2026 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) (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 D6E4934EEF5 for ; Mon, 2 Mar 2026 10:56:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772448976; cv=none; b=YECg9b6vosZxVZ9gs0e1YoGt0HDzihkST5/hGMhF3twfAFAhLj3DwIUJKVkKoGUtgPFALXAyk5NsfgEEXsHU+jNqENKBjpN2Ll+qrK7Lk86+yUjMY7Fb/nUqk5kGoElW1KC94myQ7WilnS4x8E0IKRsIwAnylZBYNR9XbpVNPHI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772448976; c=relaxed/simple; bh=KH2uudCui9HwdJauTYFJGKmjM2/Vnk7LTtJu9DXEN/g=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=esakR6/MHXimfrPchLvMbTIi4Ict+QTq6QeFFicoMeymW0CGz8AWZz0RV6BccxBaZHMk7AGv8Ei5AirIFOLbuYZl7ej3NwRaBbg8ZaEQspsHLYsaWHxixMYqvvUM1oHPshteXUQGDaFeCyvyQYKqGCi0d9/xkGkCU+kRthkcCog= 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=A3xmsNQg; arc=none smtp.client-ip=209.85.128.51 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="A3xmsNQg" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-4806bf39419so38673345e9.1 for ; Mon, 02 Mar 2026 02:56:12 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1772448971; x=1773053771; 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=4xZaOgwi6pvvEJlhn/vm/WnEuKkt2vDq71PZkVVqHzo=; b=A3xmsNQgNbHNRTr5JitmbuvlloYpd8C4O//6xYMgMIBlOXLRscc4iK/LcduHs03YXG 105VWPxiAjEbSxbSI9RDhwdAA01dQf6kMkgGqZzsJsyg9w22qtIuKG2DqMEcDjST4PqZ IJkfQBss5cZUEK9RoAJWjT4XULP6Ly9YIBI2EDWuQABw/QXFnxuaEjburq9+ang8rCAg 0yQqZagD5B7RhEO+wVK2mFDBhFXkc3G5wFBu8dtSXND2dapsimsrRwcyal7CbFpKVHeC 7aJvJW3iw5AGcpfsScA5rzFu6VG9mwfyLEo8GQiDAC52jdwm9EsO3WlI8LKeJUTh/U+O oiUw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772448971; x=1773053771; 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=4xZaOgwi6pvvEJlhn/vm/WnEuKkt2vDq71PZkVVqHzo=; b=TlDMcePokEn1TOUWHnKBzNWMu3kef5KYZlGhweVjhgzHmIN9DU6foTCk8eOrX8u44D MBYE9xFBjzk+ZxGQoN0WLWWxF9fz3GkS+PglL4W15+Lpz6FYQSbKGYueAQ5bbx/s876o a376zMEvjTSo9BJbaTAFCtQ5lOAxL55yr6HZ9udLIQP7Hu+cZ8OigphXbbErUx3i//DK b0h1MFDgO29bThKUfc4sXAcRc4YqP8RwySJwFlcODL3juNijHJfUims4WOKkTrWqjurX aTHHJArAuJxulwkTFmuFc6V9+m4dBY2dmfrrfrevzRJveAX/VB2fBB8QLrorpGAzMq7C SRXA== X-Forwarded-Encrypted: i=1; AJvYcCUuQTdKmpgu7046JU/+20E1tS9Wa1v5If0zsAM5v9VVHOzMtm+tT6jT3IYuoI+C2RtR5+HRbT6aYjF0MQM=@vger.kernel.org X-Gm-Message-State: AOJu0YzoVCDvrpmY9hiqB5s9jR5WihweHoLVBCK+VzEWT8EWcUORq4bq c52gKfOS7pa/e3fdaKo7MU1exXsTOZipeMY3gAG5sgrejNCRKws3i6nIWbmXTJ7KLkc= X-Gm-Gg: ATEYQzz9ucFM+cL4JnZfhMstEA9t6yLZB2q6k00CmYddMP45h2UlXsdB7xf5t0vZ+Lb dngoRoceKCY2i9s2y1liX6wjf1PpijAVhOd5gdqE6SIbTq5x+txwICBGp09xeqa6sZ1brCKj2it EF+vX83YxIzNjE0+16ukj4W3g7LcHwDzHpiHI+L1lpFkykJfAxX2xooEmxEiLluBMy04gbJIwHf ME+lc+Yr/TYjJpxLfgiR3bdaF5+Ki/iBXlY1m5DlXpYf8UusZexK1zdRF8qiNGL3sdPepVawmwN Pv+B9zPMaBkpAq2t5Nxfmq3Y5quJ2SERvZeeHLxF4QYFvAs7SQdS9dA9J/4dVvDu/Ndysquvj8T ksZDsaLUmdWRrwrjHrHqQrF64/5AXu151KF68IogYmsNJrt9fAc8cvvSfcHwUoXJ4wYt9MgK3LA 3XRlmPbEVVtrnFB/PKPBGgmLq5OS4X7ZRDnQQlp0oMfSUFHQuJI/VRpiIZ2M9jhIFt6wNeHCHz/ rI= X-Received: by 2002:a05:600c:3b1f:b0:483:7b99:131d with SMTP id 5b1f17b1804b1-483c993a063mr226630005e9.16.1772448970308; Mon, 02 Mar 2026 02:56:10 -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 5b1f17b1804b1-483bfccff00sm133263075e9.25.2026.03.02.02.56.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 02:56:10 -0800 (PST) From: Alexey Charkov Date: Mon, 02 Mar 2026 14:55:55 +0400 Subject: [PATCH v2 1/2] dt-bindings: hwmon: Add TI INA4230 4-channel I2C 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: <20260302-ina4230-v2-1-55b49d19d2ab@flipper.net> References: <20260302-ina4230-v2-0-55b49d19d2ab@flipper.net> In-Reply-To: <20260302-ina4230-v2-0-55b49d19d2ab@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 , Krzysztof Kozlowski X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5204; i=alchark@flipper.net; h=from:subject:message-id; bh=KH2uudCui9HwdJauTYFJGKmjM2/Vnk7LTtJu9DXEN/g=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWQuzTmpq/How8qQtavuPNNbyHyg2nfSxA17a6O+z9vv8 PxfkBtTU8dEFgYxLgZLMUWWud+W2E414pu1y8PjK8wcViaQIdIiDQxAwMLAl5uYV2qkY6Rnqm2o Z2ioY6xjxMDFKQBTvaWe4Z8aY6vetQmzvRKKAmI8FxlLh6swaNzWVojsnf8s6OrM8A8M/0yer1V Lso3zt2s9cWF/antgAFvPSr0jBxlqZ9xmlig6zwMA X-Developer-Key: i=alchark@flipper.net; a=openpgp; fpr=9DF6A43D95320E9ABA4848F5B2A2D88F1059D4A5 Add 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 Reviewed-by: Krzysztof Kozlowski Signed-off-by: Alexey Charkov --- .../devicetree/bindings/hwmon/ti,ina4230.yaml | 130 +++++++++++++++++= ++++ MAINTAINERS | 6 + 2 files changed, 136 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..69839e7a3197 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml @@ -0,0 +1,130 @@ +# 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 <50000>; + ti,maximum-expected-current-microamp =3D <300000>; + }; + + input@2 { + reg =3D <0x2>; + label =3D "VDD_5V"; + shunt-resistor-micro-ohms =3D <10000>; + ti,maximum-expected-current-microamp =3D <5000000>; + }; + + input@3 { + reg =3D <0x3>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 4d879f6a7b51..bc6f1836bcb1 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: Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml + INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao S: Maintained --=20 2.52.0 From nobody Thu Apr 9 12:08:13 2026 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (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 A576A35CB61 for ; Mon, 2 Mar 2026 10:56:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772448978; cv=none; b=SDdY2uVXr3ptWp5PGKxVgGOqpaRV/FXx6x6ECP5yTs/1MsGE+m6C7fP5y+QJsdpqqHzFb3MLFNYCMbAgVNz06jHIPuLOyUdeFAoQc5pAvhHzSsniE/uDPDaf5kr7CVXgw+bI/90jp0yRsTlHY3iNeEYuXkF/Jtsd7YbPip8g2O8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772448978; c=relaxed/simple; bh=cOM4ajXVIV/SyH8zaln2z6MlGQW9hOOg3Ra+Er72O6g=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MfCfiaZQl8Hi3xrSLXa4vwc3hmv2T8qlDv7koDKcPiUf/c9XGnLgG+7LW76+YefQx6NoeBts8Nm5qRn/30CekLqIkKRtvIbr7E78G+Og/TNBd0rPa/Y6xVOm051phUaR9FkRx2xHjueyhICb++Sn6eUclwVEZBvgYpFoub6mNAI= 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=B3qtz9wP; arc=none smtp.client-ip=209.85.128.46 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="B3qtz9wP" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-48371bb515eso65836675e9.1 for ; Mon, 02 Mar 2026 02:56:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1772448973; x=1773053773; 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=XuvQr95NB7QLg043RZ9iTkkdzJNUR9vJltDddRkD71I=; b=B3qtz9wP2uR0vvHGDU8ooyxKhaquJ/rKjTCRbzq4bZep9mzEXcDob7ljiAlMasAloP Y7xfXBVeGGj4lTmuKbB1sIYxMCwkH2xecVsMzxXMXvcCwysAK+/ry0Akut92HeYqqmcd 72QFgcP0srv0bE8X+1LJK3MgysHVNzUtQehhXCry0sWrqYLq9o7DinwxPeOTJE/9eIMf V9URLHP38YvcVaStQFYDKWVagMNutBqz1PQVnNieO94WjXrEXzRFmsnf+BLVStr31N55 Z3kfgfYgP7JgKAD5iUKUkzF2u4mjOXspVrC635HWiZQILPyhypUb+ScRB0ogrjpfvI8Y gWtw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772448973; x=1773053773; 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=XuvQr95NB7QLg043RZ9iTkkdzJNUR9vJltDddRkD71I=; b=WCF/dp4kdxQG9nHDLA44Iv+y20DHtGYs1M0aSHZQwcTxWgGvfXIoIu0AqzGl0HkdA5 n7/bICulo8IlJfsTAVc+/0L7wmw8qqIwKrOfiDvoofqKuwA7gvoNt+nbUH+iVZ6taBsy 8DeW6n0MVvAQRmfsNTViyeHAKafYBVH6Pp8pt8XJRwXBtf9bae1iNh3YHuaCIgLtqmCO E0bDUN78vZWLw+LKU2wBGF7jaU0IIn+lsPcd/OKz+QQjubAhqlS25aCHoDlxrElTeqh6 v/kflz5n11DYUVoPSPHtF7Up1MFf8ZgwImR0NM3tGWlpgVIT23gLNnKgyuJLarIh6oOS BN2g== X-Forwarded-Encrypted: i=1; AJvYcCUGFrV1WlvqUPadvBBeo41MYajPH7OyO8JIx9S7Njs/3d+OOV8Veue25+X7Gv2Bhyw2gcX5PAiKc9KU/4I=@vger.kernel.org X-Gm-Message-State: AOJu0Yw6dMQQDYj49fKqrUOUN/q45skc1vzcBCemIjNl74cCFNXQMLAi TKwC6TnKJdP9DTD8t4NDlrymGD6bjdEMBUUKqkmZif903Z3fJUD8QMU3PYKLUhk4oHNplsX1ctw xsAZo X-Gm-Gg: ATEYQzyjAvJcGKcfcwRfSNnQGT5SgN/wcpKTmypK1W6Qbm3msv4UL8Aurnknl/uTam1 /oacvynpyhglVNJFI3BZiQxMaVgT5osFlrZnduQN+mvGj/na19D50AMYs1WzBM6Hpe/tdn0sxDf jSAL9MnDxFU5VxPbTpGV82OnmhTWHHy5DNc+KZDYKWJMscead1ZbrZ711PooM6IkHUMfUALD+Wz UWZLsMcoMVuHg/EFknOuZFOet5ZVnXP0IzyfMTyw/xWP00+bvUzhXd+XC3NyR35RQnkpmcMR6mD xMXFCz6GV49iZPybIg36fOeb1Eg/jyKICeKWS3iCcsdH5FPB84IPoBDf89BpiW3GVTy6Z4y8hP7 e5VA84MukIgq+mQhy4vZBQLFIysXdoQ0NgzxYFiFCojhpC0AJOX8IcdWUK8Fz4GET6O3fsm/xtK Zm2SJRRgAOCxB95ocd12vdfvEL0lr3JeJ+8rGBniyplrYy4YDgYvKYpeLFaqu5Bf7/nzhWp3awA pI= X-Received: by 2002:a05:600c:5249:b0:477:7c7d:d9b2 with SMTP id 5b1f17b1804b1-483c9c29b56mr193869265e9.32.1772448972362; Mon, 02 Mar 2026 02:56:12 -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 5b1f17b1804b1-483bfccff00sm133263075e9.25.2026.03.02.02.56.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Mar 2026 02:56:12 -0800 (PST) From: Alexey Charkov Date: Mon, 02 Mar 2026 14:55:56 +0400 Subject: [PATCH v2 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: <20260302-ina4230-v2-2-55b49d19d2ab@flipper.net> References: <20260302-ina4230-v2-0-55b49d19d2ab@flipper.net> In-Reply-To: <20260302-ina4230-v2-0-55b49d19d2ab@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=32609; i=alchark@flipper.net; h=from:subject:message-id; bh=cOM4ajXVIV/SyH8zaln2z6MlGQW9hOOg3Ra+Er72O6g=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWQuzTn5lN/j686t+67JLO0u2Z/QLM8xPfHVgrbuPUt+b ExnkbLU7JjIwiDGxWAppsgy99sS26lGfLN2eXh8hZnDygQyRFqkgQEIWBj4chPzSo10jPRMtQ31 DA11jHWMGLg4BWCq//UwMsxNvz8n+J7RPq6vS35lr0tyXvfyz/YrgjpqNaaPZ0Y9Zs9j+Cv80/v 9XQPVNO6zDTtOlzzdGTXth68pg/5jDh2R7R9XSbADAA== 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 | 1 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ina4230.c | 1006 +++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 1019 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index bc6f1836bcb1..37939448ea0c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12516,6 +12516,7 @@ M: Alexey Charkov L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml +F: drivers/hwmon/ina4230.c =20 INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao 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..9b4148f0758e --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,1006 @@ +// 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 +#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; + u64 n, d; + + 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= ); + n =3D 5120000000ULL; + d =3D (u64)input->current_lsb_uA * input->shunt_resistor * input->shunt_g= ain; + /* Ensure rounding to the closest integer */ + n +=3D d / 2; + do_div(n, d); + if (n > INA4230_CALIBRATION_MASK) { + dev_err(ina->pm_dev, + "Shunt %duO too low for expected current %duA, cannot calibrate channel= %d\n", + input->shunt_resistor, input->max_expected_current, channel + 1); + return -ERANGE; + } + + calibration =3D n & INA4230_CALIBRATION_MASK; + + 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; + + ret =3D ina4230_set_calibration(ina, channel); + if (ret) + return ret; + + /* 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) + return dev_err_probe(dev, ret, + "missing reg property of %pOFn\n", child); + else if (val > INA4230_CHANNEL4) + return dev_err_probe(dev, -EINVAL, + "invalid reg %d of %pOFn\n", val, child); + + 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) + return dev_err_probe(dev, -EINVAL, + "invalid shunt resistor value %u of %pOFn\n", + val, child); + + 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) + return dev_err_probe(dev, -EINVAL, + "invalid max current value %u of %pOFn\n", + val, child); + + 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)) + return PTR_ERR(ina->regmap); + + ret =3D devm_regmap_field_bulk_alloc(dev, ina->regmap, ina->fields, + ina4230_reg_fields, + ARRAY_SIZE(ina4230_reg_fields)); + if (ret) + return ret; + + 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) + return dev_err_probe(dev, ret, + "Unable to probe from device tree\n"); + + /* 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); + } + + ina->pm_dev =3D dev; + dev_set_drvdata(dev, ina); + + /* Set calibration values */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) { + ret =3D ina4230_set_calibration(ina, i); + if (ret) + return ret; + } + } + + /* 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)) { + ret =3D dev_err_probe(dev, PTR_ERR(hwmon_dev), + "Unable to register hwmon device\n"); + 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