From nobody Thu Apr 9 06:37:28 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 2FD1F389462 for ; Tue, 10 Mar 2026 11:43:55 +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=1773143037; cv=none; b=Tv9A8VBES8o7N2aZxGJWRPYcV68Gsqaw7KQ8/sIHkMkVr9XaSbHj6Y/Ittor7z8zWqqquVkDHvngYWQyZOl70O346Em7yAXebVX6FkWdSZYqEVxbkLaG1q4C9+qddDw3jP7HczB0chptMSqtuC2ifSwrz6nL6EZi7B5WXS/rvpU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773143037; c=relaxed/simple; bh=vYR6R0xAlGyJOKPka5f5e+VU0LDMPSCdIMPETAplrI8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qq5KcD66YxtZ6eRxJR7z0cRyCglmQPAQCilKxvxMhNuEjP6VADcOKzxpX2gHdxinfKC69xYnqpDukNIkcVZ07QvPQsDNVoKxMbWLUP22tP9GbmYM/VbapjUBzVFbqTNJc8Ewmb5Y2lUb+/1Ax5T1rrgLhKWxL9OA81DJ4h/vZDg= 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=KEhz80wH; 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="KEhz80wH" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-4852a9c6309so33052675e9.0 for ; Tue, 10 Mar 2026 04:43:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1773143034; x=1773747834; 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=FUaHHGjX//QqgU4DYt1eH54CJSDOOAyxvdOGOgcLp5o=; b=KEhz80wHM33B2aZGDfOVmVEFg9vFxtiki//7PG7Rob3owNkRkxqHBlCMdRg7NoU60c QAKAtpX/BeKG5WNsB2cVxnYbBN+BSeqjmPblj23oWMj5g5pEIr5eiCdEaXyjl/10OvpW AGXwQ2WU8Gv5cagIKOr9MuspjD6hVjUU5mocXwz23/2z1+oI89ocmai6pZDltPzk37ME tnfiIHm5CgHWnl+rrlM0ZqHLQ8ToecNJ7CzxkwQqU3bg3rZs3sLI59mBPi5mazkkvxTl 4Aix3U1hPUo0xI0c7L9CsK3/0k5BlTHxv3AloVfiX7ysXjZSjHGYwdObREBdloWS9JGh 923A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773143034; x=1773747834; 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=FUaHHGjX//QqgU4DYt1eH54CJSDOOAyxvdOGOgcLp5o=; b=Ve3LeI95ktrpkLB7/Q01NIkU2u4o5zxQ87y6LqTpH/4e2WJWIFP/CoBW3Khv9dB5HR 3JZPi7fEaxYm3zvB8EoBm3jyVTMD/gAPvZTAr6jzYfnwXIFAe37wltflWSw9SGVy4sie oHN1BNlUYSEO4CnreC2YcA6UZnN6l9ubD0WEPCpo/b9v2ccNGL9wZ119r1JW9nP5HVoB ufrTqyGqqvTBB73UdyCXl4CDv4Cbsb1pkh1gtNAGP10v7zCyVwGwVrR7IjJAKLKW09al /SP0un+mVx/pmPJTYiJ19U1+v4SJPPKvbXZOoVZOPU3tOubqfkToAgCTw86eVnjeSz0j sxsg== X-Forwarded-Encrypted: i=1; AJvYcCUWPDFGCEPTqllju3F3L9IHKTQb9kVLtA5eT9d9QgDHmzZ1sGCeNDNAGB4iSeyzwyuW4wEdS1uFKm2GMNc=@vger.kernel.org X-Gm-Message-State: AOJu0Yw1a9VwY+eqlUcNF70UxNFZXfwnkw1gXH/U4RAxednvWSIDtkit kT682Stb7UxM3vBR2QC+dpE3C5ptMuHnzx7bY3m7K+5y7KYdyWelH2bXZ/o/Xoi0uzzwuXYIQGe bgjY/ X-Gm-Gg: ATEYQzxYZ2tGPZg+4YUprVYQXh4C5mfYrmZLuMfkXTiveulaDPlcCu5zHgL4ZaXD8Kr uFPSXP0SfVQZW6NGrsBcg5y23zvdT3ku9e0pQ76meWpIvDI3lOxTJLnCZuakGsAmH+exbyMTUcr 7K8j7yA0E1v+yuOWJx6k66MAKwuZnNesgRwooFAr23xhfefCVQ8+6mu7udR6XUavaegZfj5pM6P nmoHOj6CH3vHeUdhgIo2SHHOpkTte/DV7UWlC0Mv/A9YdisAYVnQiawaGdYJ70b0n0UmtXSDOV/ EePxvzUDdLCEb6Z8EvFvmTK0wXghApdY5yqvjeu1uvhHQRuBHSllJiTBZ1B3M0xqWHYdAWqoH16 1LWCn9kWrbdUBIBVQ013LrTlh0nK2RPGxj+9u+5bGN70jFBUbhHwaAbGbYp+ob+waHAayzN7jp7 Qt21uapX9aY+Hanoas5GP3PyjAgfoicuA/TEBCoyFs9vVS06HtbZsQmJ2CD9BrbxBA8YuybmDt5 aKERw== X-Received: by 2002:a05:600c:8b26:b0:480:1c85:88bf with SMTP id 5b1f17b1804b1-4852697a5c1mr238645245e9.27.1773143033512; Tue, 10 Mar 2026 04:43:53 -0700 (PDT) Received: from alchark-surface.localdomain (bba-86-98-192-109.alshamil.net.ae. [86.98.192.109]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48541a7f182sm96234825e9.5.2026.03.10.04.43.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 04:43:53 -0700 (PDT) From: Alexey Charkov Date: Tue, 10 Mar 2026 15:43:46 +0400 Subject: [PATCH v3 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: <20260310-ina4230-v3-1-06ab3a77c570@flipper.net> References: <20260310-ina4230-v3-0-06ab3a77c570@flipper.net> In-Reply-To: <20260310-ina4230-v3-0-06ab3a77c570@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=5501; i=alchark@flipper.net; h=from:subject:message-id; bh=vYR6R0xAlGyJOKPka5f5e+VU0LDMPSCdIMPETAplrI8=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWRuYP6x62joNA097RW+e/t9q8su6uZpVeaHZ11ZdOv2E eGpLyKzOyayMIhxMViKKbLM/bbEdqoR36xdHh5fYeawMoEMkRZpYAACFga+3MS8UiMdIz1TbUM9 Q0MdYx0jBi5OAZhqo+0M/+ziHyffnSDgGjHj0dozRhtkvuybvCb9hdiSav98Q9c3avsY/nB7iyu 53081uaScW5DyRPv0tdYv+41Nvzhn3HO5Ibb1Oi8A 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 | 134 +++++++++++++++++= ++++ MAINTAINERS | 6 + 2 files changed, 140 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..f33e52a12657 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina4230.yaml @@ -0,0 +1,134 @@ +# 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 to maximize= the + available precision while ensuring your expected maximum current= fits + within the chip's ADC range. It will also enable built-in shunt = gain + to increase ADC granularity by a factor of 4 if the provided max= imum + current / shunt resistance combination does not produce more than + 20.48 mV drop at the shunt. + 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 1121276c59a1..10a330c5b44d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12516,6 +12516,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 06:37:28 2026 Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) (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 A9F5E38A703 for ; Tue, 10 Mar 2026 11:43:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773143041; cv=none; b=XSpCiEbW6nszzk6/pTwNCXA1FFxLXgqUH3hI03T++96ZUxT09sIAe2M1urjG6NpDfHf78kSsJgk9mWlLWtpys7P2m6NZ3OglU0umDDFOSBlvM6xt/kz3JtuRer45TL9WshmASLcRHlIEt5AMV2jlkFSgV//VXeWz63yWqEufVs0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773143041; c=relaxed/simple; bh=G3MtqMKafpLNucg97qxshZA/N6KPWJSwt2+v5K33rWA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YpCuziUqBR0fhasB6px5TXR0GG34ve/RBdHC1QgHhBGPr+qyfgn/E7AKhvGDzQdO4px13OrWCjRq1jG7/cNBK8kLUboN655P+z3caaw/zPkihDyUuV47vOQN80p5XI/XodxT3aX7JqcaT26haVeHtiuLnNNaXWPQBoEwKGLyI5Q= 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=Eqs3HZUu; arc=none smtp.client-ip=209.85.128.52 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="Eqs3HZUu" Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-48540355459so14590145e9.3 for ; Tue, 10 Mar 2026 04:43:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1773143036; x=1773747836; 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=QSURpBzaWzxRVKWicQ4ojOkEk7LCz0gq/IP5lnEmszM=; b=Eqs3HZUugjaw8TbvP/KDpQcc1l5kGu30xdT1CqNl7vtgGAKIt4B8Txp8nn97XN6Cdv iiI7+fheJKfdAMCPA87QajxAXE/WkWSBu4lg+lP84GeObrmz8yi9jMYSOAPKmaZLlKc/ 7LYGve31AZyUFR5PaUfkNVd8+XTnpTlgY3eD41kzVAFgcky3kopQGGiBodvhOV+9ImfP P+uwTygdihJoELXLepXWTr4waqfd0krdvmhfJ7W4lAqmFf7z8bnVOtJLv5A7MCZR6lLW OewCN2qd+ahjwUVuM08UY0aZ+Bt//yEy4uRUcGrd5SjS8LYG3PUnh4gLJdd2B5hObgOb 1r1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773143036; x=1773747836; 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=QSURpBzaWzxRVKWicQ4ojOkEk7LCz0gq/IP5lnEmszM=; b=wrHMSM3IV6QuO4Eiyc7PJtNXo6kze4DSOxpqzd3hMips4dufBc5naNX9oiX9bt0uEI OVLEv82hPHsm5tZmW3Mr14pimr8g8CeTGvf5+2HtwE32UAXcs/s5Du8WTknsuCu8NN6M C3i+IGSrCfIjbMaCopfG6/3z0VV5HpxPUhbGXPd97HdB5WQFJnt5Yd9DFymgD4+Uu8CL Y20tkoCnJnpqvxIrnb+M3uQ4157s00+MqF7nCYgEcFh1IW1Uus+MIQYfRp1GlnbYrZQy VkV1vgKe3oo+zgLWnoq5YTArll19DcrHz3n3FLpANL5elgSLOXnBf0ibo1j+JcJ9KjmN mKhQ== X-Forwarded-Encrypted: i=1; AJvYcCWeuQtJ1MJEWroA0TExzG2g12lUoFeY1Osy/Kg+iKUPX8RmfPoIs+ZUOfoLFzwADwiLyU29MDmBvbAjGwI=@vger.kernel.org X-Gm-Message-State: AOJu0Yxp5UK5F+vexdd3ez4yvCvjmm481SiVKHVSxed//NH8EUEQxeWo EhbNj9XAVheUX25luR5UF0hicrEXi1t7wU/7t0TVv+zpPAj8asrA49BnMXHtU42KvNg= X-Gm-Gg: ATEYQzwVGmVyeXsnigkty9kjKVERu5scWSWspMf1W6Dkte3grOV3/uoaSn8Zi/5bLS9 aqf4zwH6lTzywNc14p1FaeJ8RojALHp7Tx71w1PjY657FP1HUxUloEBYzrGSinS5uTe2M+mA5ff z7dTfnPKQ5AHgSiycfILBtoLCEdKg+m0IbNiLlEoWyBvBnnoEl0jZPCZIOU38o7AfX9Mv6Y/Ofg oQ2fTWRK19p0UgXtP7xo50uCs8Qtey0EPDWHXgLRuJf9cOykMyk/7/YADtR8MNWvHlFJSa1yrhZ EoNhcuTc5lgnfhQ7JwOfXs5N3FvQApcK/dATuTsedlOX/WPnWhXG4yDd0rDLpTqEuB2tZoRTZXn HdLHlUj1RtoROCtcE4ZihV8cfsZoPQo/fYgEJcIwC5SM+boFsLhJLcYFJBDVSYWdRANIzYHc9du Ngf8xLjB92pbjZ2x29mNa9rRjgCcR1HRRYJ/O0Hd3K0UDvWbdBlC9IBWZ2Lu1+JuH7eL01FWetY Q3aKBqeZ3pGTe5t X-Received: by 2002:a05:600c:19ca:b0:485:3d43:7c9a with SMTP id 5b1f17b1804b1-4853d437d40mr91462945e9.25.1773143035590; Tue, 10 Mar 2026 04:43:55 -0700 (PDT) Received: from alchark-surface.localdomain (bba-86-98-192-109.alshamil.net.ae. [86.98.192.109]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48541a7f182sm96234825e9.5.2026.03.10.04.43.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 04:43:55 -0700 (PDT) From: Alexey Charkov Date: Tue, 10 Mar 2026 15:43:47 +0400 Subject: [PATCH v3 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: <20260310-ina4230-v3-2-06ab3a77c570@flipper.net> References: <20260310-ina4230-v3-0-06ab3a77c570@flipper.net> In-Reply-To: <20260310-ina4230-v3-0-06ab3a77c570@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=34132; i=alchark@flipper.net; h=from:subject:message-id; bh=G3MtqMKafpLNucg97qxshZA/N6KPWJSwt2+v5K33rWA=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWRuYP7xK1c154NN8yaZ5Qlu5yZaJjKJvGm0a+N8dOfOi SSu61U8HRNZGMS4GCzFFFnmfltiO9WIb9YuD4+vMHNYmUCGSIs0MAABCwNfbmJeqZGOkZ6ptqGe oaGOsY4RAxenAEz1hAqGf6YnU2flzflsXfRzx6n7tfU5K6e53nILF8j1aK5gYpM5f46RYcMhmwI jQ+u+pvIvH450BgnO7uxdsCdVt4L18M6VMUIyTAA= 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 | 1066 +++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 1079 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 10a330c5b44d..7d9c5cd667a3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12521,6 +12521,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 fb77baeeba27..06c151ff8a60 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2297,6 +2297,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 556e86d277b1..3d83eba94bec 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..7e5c7fe2274b --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,1066 @@ +// 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; + 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; + n =3D div64_u64(n, d); + if (n > INA4230_CALIBRATION_MASK) { + dev_err(ina->pm_dev, + "Shunt %duOhm too low for expected current %duA, cannot calibrate chann= el %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) { + ret =3D regmap_write(ina->regmap, INA4230_CONFIG1, + ina->reg_config1); + if (ret) + return ret; + + 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 + */ + if (is_shunt) + *val =3D mult_frac((long)(int16_t)regval, + 2500 / ina->inputs[channel].shunt_gain, + 1000000); + else + *val =3D mult_frac((long)(int16_t)regval, + 1600, + 1000); + 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_noinc_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; + + /* Write CONFIG1 register to trigger a single-shot measurement */ + if (ina->single_shot) { + ret =3D regmap_write(ina->regmap, INA4230_CONFIG1, + ina->reg_config1); + if (ret) + return ret; + + 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_write_enable(struct device *dev, int channel, bool enab= le) +{ + struct ina4230_data *ina =3D dev_get_drvdata(dev); + u16 config, mask =3D INA4230_CONFIG_CHx_EN(channel); + u16 config_old =3D ina->reg_config1 & mask; + u32 tmp; + int ret; + + config =3D enable ? mask : 0; + + /* Bypass if enable status is not being changed */ + if (config_old =3D=3D config) + return 0; + + /* For enabling routine, increase refcount and resume() at first */ + if (enable) { + ret =3D pm_runtime_resume_and_get(ina->pm_dev); + if (ret < 0) { + dev_err(dev, "Failed to get PM runtime\n"); + return ret; + } + } + + /* Enable or disable the channel */ + tmp =3D (ina->reg_config1 & ~mask) | (config & mask); + ret =3D regmap_write(ina->regmap, INA4230_CONFIG1, tmp); + if (ret) + goto fail; + + /* Cache the latest config register value */ + ina->reg_config1 =3D tmp; + + /* For disabling routine, decrease refcount or suspend() at last */ + if (!enable) + pm_runtime_put_sync(ina->pm_dev); + + return 0; + +fail: + if (enable) { + dev_err(dev, "Failed to enable channel %d: error %d\n", + channel, ret); + pm_runtime_put_sync(ina->pm_dev); + } + + return ret; +} + +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; + case hwmon_in: + /* 0-align channel ID */ + ret =3D ina4230_write_enable(dev, channel - 1, 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); + + /* 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; + } + + /* Set calibration values after device resume/reset */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) { + ret =3D ina4230_set_calibration(ina, i); + if (ret) + 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() for connected channels to balance get_sync */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + 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() for connected channels to balance get_sync */ + for (i =3D 0; i < INA4230_NUM_CHANNELS; i++) { + if (!ina->inputs[i].disconnected) + 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