From nobody Thu Apr 2 18:47:53 2026 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (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 A74C539525B for ; Thu, 26 Mar 2026 19:59:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774555163; cv=none; b=PQA4uujo+5Xt2QNtdWHzDpHWWpZcArXtl8Oh+8/M46fErrz+r0D+JVMpArhEDAuwauxWcT03K+RQzzFbwfh49I6QOpj+wBAX24FjQKyJjoxgfoSC8OGIRhRZRpgqKVjdQO6IOu/7khKLC8gQhlHrgV1LyL1vrOiI3Rw5lfE0x2w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774555163; c=relaxed/simple; bh=pg18cJFdoxIiWqhryWU41mtt2pcNId3Z0xRyUEkJ7Js=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=jLLoQotyeBKzlGr+4x6GK/kOvR90CXjQmmHWaBfQPk0dUou2i3G6vUxCYyQfzabi1kUrMFh0ij4qG/3aaWGrWI2YZiOAN7juo/m2ddQgmUe4N7eO42loWA1bdE5pSP+hsj6KXffGVykUnTQRXgTyQelSGYoqN8LNP7phMpMvFDM= 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=PesAIS4F; arc=none smtp.client-ip=209.85.128.47 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="PesAIS4F" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-48704db565eso19826695e9.1 for ; Thu, 26 Mar 2026 12:59:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1774555160; x=1775159960; 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=E3dA0/OmXMTjuCFxyS+Ww0yEAOk1EYtLRPmvSUBU78k=; b=PesAIS4FCJ++jn4veTDp41XEI0PDOtH8NCXQeMSkuA3xNCJP7kYVF3K8ETwi9ZPcAA rkB1tGJANOrPGjJpEiJqwETNVdUq7dCcCGt4jZVlZ9Z78gwafIILtKpLP8XlTcYh1//C Qk7n52kqx5g4TvWr5zJYtz78PJTimgMG1o+txdqO9ayH46LWqKaUJOPNg4oUxe5ZXDvc kkNO3cJxNSAJbFQnpFCo4nmIB/hKndRgAXJq37L5gky0UuypMfVO0feLB3uUkqiU/mG9 0GTP9GYluaLN+gw5b/feBDVLMZP68VOjxIyRPRc2jVvwvyWjaHFe+XhzNUrxqE8cnuAb LLPg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774555160; x=1775159960; 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=E3dA0/OmXMTjuCFxyS+Ww0yEAOk1EYtLRPmvSUBU78k=; b=Su141nsMvsarZ78brx2z6Y36tVpz6khlZ8vE4eUWnljKe8uDe6JgTcYvxYfYIuLb1B qFcCz/GPAmGAxz2q0zY+3M2lgRqAT6aPinW5NJT9VkPP8cxjmHDZ31v+Sh71vi8VMgby 7sVvyfh1VGhzGGfUMcgXs4KJkKTspXkxOceuAtMqCHVu/2NRlu4dLYSCPxiFKkIGEYXX u21yM0pVb2p44rmYa3JSFJb56gvHnMgdoB4Ij9F3aWC30CnWHMUwc24UNDYxR2Yb4CrI noFJD1CHMKOodo0ca35kE+i7Qix0/JkmahUZGd5jL1bSr2Ma/aK7+sbtnrbNh8lashNq w9SQ== X-Forwarded-Encrypted: i=1; AJvYcCUyBnFFJwaYhr//Ldge9NOMb1jKmGMsrDfa2dfH1PR3b7DcKCS6gTot9mcmkRbk8cVQzvfInEYvkdZnoQo=@vger.kernel.org X-Gm-Message-State: AOJu0YxpHAZ0ounsJOVfpg0jnQu5ex/JojZX6ebIVJ64hcY665F216YQ EhBRYMGJo0+B8uMIqiLFtAns26WXUtDl5xhUNMilLQDWjPeNYUauUAQmmhz1vNfU/dM= X-Gm-Gg: ATEYQzxXH8yTbFohrBFczUMzX4XmjqzlrhQuNFCx6q0ZYmOsEA5gLYjOpP8DaHMOZe7 oJ/9zP8Zii+hOTLRYuZLeu+/VTyF+DeQqJEPe7f2XQ6IQvkYjuu/YsdbaAZiTXNNYZlz/YUbfJ2 OwyT2X0LA3rrdsSkhj2ATDWAhpg1FSR8Br2wdIiQUyvOKs0oskYCpm2ZrJMghL4Tq5U5tIlJZqo /0SQRXXyQGYWfdKW0WKEVJGRJM1MLbjfCYrb00S+x3FZ9tB3EwfOjMRuueRSPDMrsBVpzk2SWVZ mBRaFdV3q6sNzjTadEIGNY5xKaYanp+Uz1trXvIVjf2ymEGjxgSV1VRcPxqqzzRd5vWG2295toq vzzLO3Go4Mt+oA83whgxeRypS+D6QAPGPryqku/7ZocHisDE475XatAu2B4gFr/T824vUBKmwWb AQ7ZBqrfzkIkIAEQIcyZt+41IY/enxFooza8y/L0/MocpDr1Kq7gHgacP/6STQrOo0KWIQryfyp in6Hg== X-Received: by 2002:a05:600c:8106:b0:485:3f58:d84 with SMTP id 5b1f17b1804b1-487160b496emr141865795e9.32.1774555160110; Thu, 26 Mar 2026 12:59:20 -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 ffacd0b85a97d-43b919df7dcsm11339012f8f.27.2026.03.26.12.59.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Mar 2026 12:59:19 -0700 (PDT) From: Alexey Charkov Date: Thu, 26 Mar 2026 23:59:00 +0400 Subject: [PATCH v4 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: <20260326-ina4230-v4-1-c1e312c09de7@flipper.net> References: <20260326-ina4230-v4-0-c1e312c09de7@flipper.net> In-Reply-To: <20260326-ina4230-v4-0-c1e312c09de7@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=pg18cJFdoxIiWqhryWU41mtt2pcNId3Z0xRyUEkJ7Js=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWQenSCy/sYXkcdX327ZN2fSbeO7euY750br3OVmOcmu+ ayI44bf9o6JLAxiXAyWYoosc78tsZ1qxDdrl4fHV5g5rEwgQ6RFGhiAgIWBLzcxr9RIx0jPVNtQ z9BQx1jHiIGLUwCmuuwGw3+3cx1N64xXBAi2cIT9yO2UmlSYuWDvuxMPGMsvN0qtfLKG4X+M+9O HE+vubXzSGVW646HS5/e3Q6dtvRP3g03ni4ih7XwuAA== 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..bed45c413206 --- /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: 2147483647 + 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 9fbb619c6f42..3204e1b8753e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12536,6 +12536,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 2 18:47:53 2026 Received: from mail-wr1-f53.google.com (mail-wr1-f53.google.com [209.85.221.53]) (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 0F1DC3A451A for ; Thu, 26 Mar 2026 19:59:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774555167; cv=none; b=TPkehVMVBtc0RjRPZkf27YuRxFQ4b75ZC1VlqWGWQmPHpp9oPry86ObDCHUS8/run2+n1XpWii9G4pdl7qKBfLlL8WvhyUJXoePqcwQv760xHCgJPtC9j5Kbqr0n5Aqp7hIQtenFq0Jef/hjaUNJ4wXau4rSLEIe7okhhytFjB0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774555167; c=relaxed/simple; bh=qQYbv1H72eKskUyixgO/sf0YvX/17930+Sr8cGbj7EE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=st9b6RUiNPcYo3QT8w0S0IPf9z2IsoH3JBduXEQ2RJ8ACPE1pwx7BhWyFZBn4F9d+P+DRzz4pJW1XMc9KHWVjnTof4TxSpdAl6OPjFqClm3QrYV0mycvTzFVTLaqiCrQQBIFoFkSeAgJtNtIneWYC3uOgoToUsfNEZHgCD/Xpd0= 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=yO6MZr4K; arc=none smtp.client-ip=209.85.221.53 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="yO6MZr4K" Received: by mail-wr1-f53.google.com with SMTP id ffacd0b85a97d-439b611274bso730038f8f.3 for ; Thu, 26 Mar 2026 12:59:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=flipper.net; s=google; t=1774555162; x=1775159962; 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=2uPmFEt2czqplkPqslGnNPWLSsjnezzoEDwOV0HkHiU=; b=yO6MZr4KSEYxkviKNNowD4TvXA6oW6Vz4eQutPjkRAlFkhAE/qKG1DiOprID3T8mec l/2+MYh/epoQOo+vmzTr9wi3+FoQmaNrq19In8pDdg8J3eMYppLc6VRawWlnOOqo73q9 CMDDS6JNSsaM+W5Vdh3HIIdHvaAlBrzXJzhXP4uv8UT5Q+kT7xHInSzLY85wFJISBeum vqH2aYkjMfsaDGIzf023NITkOzNKltwSg5MhzLKUFb0wEqjc3G5uE9dx/AEHVVt3CKJ9 L+3KtAeS5lRL4MTViEKEoMK+yj3oL5rSlJgA8C7lGRQVCQI5cgVvP0NuoIpKs9sQBciX oDGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774555162; x=1775159962; 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=2uPmFEt2czqplkPqslGnNPWLSsjnezzoEDwOV0HkHiU=; b=Cqc7K8dEk08oZAJp35BO3dh+CGTpWoVIF8cl2jXbiQRvATzkjXUbz8BtFAMCFrMjWb PW03iX/8EcZbYVvMEUv6+aMJCFrezLbem8AnwIoGt3Z0OjvbYEW80VtE1JuD/ZgMfqO3 uZ/z3U/Fzne6eD4Pd7Id67gcMk+HNcfkSZJirMhd5l+24jFg9nhA6Gla0ires19NrFZM Pk0Qeo28n2to0gdiN1vn7JLHMiE6shC0viqoNuasYiIXp/4ntaZ5ek3v/yBXcz7CTgEH Y/hbN7rt/Vr13AyHStDbaozpaKYFTF+D9zZvy+JYapJDJtS7MEW5Ww0fIEMS1t43+k5v SWcw== X-Forwarded-Encrypted: i=1; AJvYcCU6y2FKHoBfoh9S17n8PanW+/AsDglMknHlEK7bHiiMzi5b1dzeVQwh2nroWZ7MOWQ3FGfizjnEqlB8bJI=@vger.kernel.org X-Gm-Message-State: AOJu0Yz2vGQUh6yvxziaONTOMx4Oq9/IG5muNHK7o1bAhlz19DM9qSqI TmDi05rYaVHmSh8EiyFmVF+5JYfq+6P73L7vk/oPkKjgJHAqX44gl/LKqjmFZzbrcjo= X-Gm-Gg: ATEYQzxjYBPIiVUIFdxJtBTBWaH3SQPOYo/4bHhaIsSIFTKVCBGlz1M0Y4tWfL6okDp aU2UZ4BzJ/TMqwNAVh/2pU10lDvX6RkTtp/E7RqIMJtATdM5t2CRXEZ2hjF7NOvinv+SqMmyyDb nv70xMn6IMJ8BIEhxxqfsQ0C65ozBB/icsaJvW6p4PaHohnBKcphDK/JhyKB155hbhA5X3IE+cG KaBvNGc0E+HCBg36sYvy7ubgHLGBaEnSbhBvDPZ/S3c7nIcwvhwQAaHcw5xEkNuHkG763LqfAej 6QGhSxb/b88B25dvz4Kn+um5tIVGaQwE4EBOSrtGDLbzTD0wh1v1Joi+E/y1vaEG9hJ26tUXFtu gkfxIwDpOZK9CZupJpvOplwCCY/b++ChBSBIH9c6VV9UD+QGZ0nWvpw6u4Y/jWA/sBH5IVTKVOS 28Q7BiBLOXxZzWR7zMd6fYiCZNqJywuCl1Vo2YNlcqAjH6Ego+8p7UIE/GvbgZNduS+5V261DGh iZ9yw== X-Received: by 2002:a05:6000:26cb:b0:43b:9903:2ba3 with SMTP id ffacd0b85a97d-43b99032babmr3890118f8f.33.1774555162133; Thu, 26 Mar 2026 12:59:22 -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 ffacd0b85a97d-43b919df7dcsm11339012f8f.27.2026.03.26.12.59.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Mar 2026 12:59:21 -0700 (PDT) From: Alexey Charkov Date: Thu, 26 Mar 2026 23:59:01 +0400 Subject: [PATCH v4 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: <20260326-ina4230-v4-2-c1e312c09de7@flipper.net> References: <20260326-ina4230-v4-0-c1e312c09de7@flipper.net> In-Reply-To: <20260326-ina4230-v4-0-c1e312c09de7@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=33238; i=alchark@flipper.net; h=from:subject:message-id; bh=qQYbv1H72eKskUyixgO/sf0YvX/17930+Sr8cGbj7EE=; b=owGbwMvMwCW2adGNfoHIK0sZT6slMWQenSBSeefgfakkU7f/XoUJGxXUJbvtNI7Vrr52MPlf1 eySnhT9joksDGJcDJZiiixzvy2xnWrEN2uXh8dXmDmsTCBDpEUaGICAhYEvNzGv1EjHSM9U21DP 0FDHWMeIgYtTAKaaM4GR4cNDY93ci8nvH9h8Sz/h2FzsPCHDKD75SnH3/mmn5/gfMGdkWHohP1n UuUvF6PiLRWmv41zbNjG/9nl35Vnwf4GtOa/+MgEA 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 | 1032 +++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 1045 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3204e1b8753e..7cbf662d7d29 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12541,6 +12541,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 9d49cfd4ef3d..4649f00f24ca 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2296,6 +2296,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..b5233c004089 --- /dev/null +++ b/drivers/hwmon/ina4230.c @@ -0,0 +1,1032 @@ +// 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 + * @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 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 && + ina->reg_config1 & INA4230_CONFIG_CHx_EN(channel); +} + +/* 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 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; + + 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; + + 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->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; + } + + ret =3D devm_regulator_get_enable_optional(dev, "vs"); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + 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->alert_active_high) + FIELD_MODIFY(INA4230_CONFIG2_ALERT_POL, &ina->reg_config2, 1); + + /* 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