From nobody Fri Jun 12 11:28:12 2026 Received: from esa.microchip.iphmx.com (esa.microchip.iphmx.com [68.232.153.233]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BDC3A38F925; Fri, 15 May 2026 09:30:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=68.232.153.233 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778837402; cv=none; b=B9OSOAiA3M3sLeQGQBmcLHV7YjOHQgipab/zGUa+/5PW0RzYMtqDurXcAjuGn3YkJOVrgk5xvtRxfZzAhYUQSK4V/Ervky0zikI1Dgcg1OAOMohHXTjAnWVp4srbC+FC8Fq7MnygcFp50ND4KgwwHsayywfBytYDd0golRajBuo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778837402; c=relaxed/simple; bh=OPKmCAPMJ7Oifes6noXEBJhoydezDZBQBd5mA5U2Kvc=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=guQu4yK3Umr8LVCoOGuntRPAuTgGB6oqhsxieW4ncmOIq6E+V1UTlUzgrHM6voSftrU8JMaVQlBNsdExfPy/yWdSSk/8ktVQC9/SiVHQ2KXUB48a7Ow3FFW0yu9qwHLp8EtgoWZaOnHuE+JU3yKeUqiCW9RSoWogx6WPX9Fes3M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microchip.com; spf=pass smtp.mailfrom=microchip.com; dkim=pass (2048-bit key) header.d=microchip.com header.i=@microchip.com header.b=iiNM/2sl; arc=none smtp.client-ip=68.232.153.233 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microchip.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=microchip.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=microchip.com header.i=@microchip.com header.b="iiNM/2sl" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=microchip.com; i=@microchip.com; q=dns/txt; s=mchp; t=1778837401; x=1810373401; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=OPKmCAPMJ7Oifes6noXEBJhoydezDZBQBd5mA5U2Kvc=; b=iiNM/2slCTokeHI99s0FN9HZbr8DnCzjoW/BvNSJcNwwCp+lYea5ZF2z TYl+NrpSOxHmvrAV//eEKFOmmCX+jBnmInhOsxqS9raDqZ25I0Xw5Lb0G bTPgiRaD13vc1pzgjWI0XJSN7IYHAyG+VmZCNNVh5hx9uMkRx5ereWzUw Mr+BcWUkWUwssQSCjsEBxpDJCVIeTScFkxMWCJwKchtxjne+7bhSU1FCd 6TnJD3TAEtwyyIHQWnXeolXPOngGd/MJqvz3cDzOL03i7b6Z8zl2OE+Ha 68wt8v27NblWktOC1B+78vpwAZkFaSOP9Spaxqm/Gz+ZbO8RaGf8Fagrv Q==; X-CSE-ConnectionGUID: sjdS2eLzStWCAF3U+sAbMQ== X-CSE-MsgGUID: BP5bujAQQL2JwhsMKV+Asw== X-IronPort-AV: E=Sophos;i="6.23,236,1770620400"; d="scan'208";a="57560893" X-Amp-Result: SKIPPED(no attachment in message) Received: from unknown (HELO email.microchip.com) ([170.129.1.10]) by esa3.microchip.iphmx.com with ESMTP/TLS/ECDHE-RSA-AES128-GCM-SHA256; 15 May 2026 02:30:01 -0700 Received: from chn-vm-ex03.mchp-main.com (10.10.85.151) by chn-vm-ex01.mchp-main.com (10.10.85.143) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.58; Fri, 15 May 2026 02:29:59 -0700 Received: from marius-VM.mshome.net (10.10.85.11) by chn-vm-ex03.mchp-main.com (10.10.85.151) with Microsoft SMTP Server id 15.1.2507.58 via Frontend Transport; Fri, 15 May 2026 02:29:57 -0700 From: To: , , , CC: , , , , , , , Subject: [PATCH v4 1/2] dt-bindings: iio: adc: add support for PAC1944 Date: Fri, 15 May 2026 12:29:45 +0300 Message-ID: <20260515092946.10791-2-marius.cristea@microchip.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260515092946.10791-1-marius.cristea@microchip.com> References: <20260515092946.10791-1-marius.cristea@microchip.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Marius Cristea This is the device tree schema for iio driver for Microchip PAC194X and PAC195X series of Power Monitors with Accumulator. The PAC194X family supports 9V Full-Scale Range and the PAC195X supports 32V Full-Scale Range. There are two versions of the PAC194X/5X: the PAC194X/5X-1 devices are for high-side current sensing and the PAC194X/5X-2 devices are for low-side current sensing or floating VBUS applications. The PAC194X/5X-1 is named shortly PAC194X/5X. Signed-off-by: Marius Cristea --- .../bindings/iio/adc/microchip,pac1944.yaml | 315 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 321 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/microchip,pac= 1944.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/microchip,pac1944.ya= ml b/Documentation/devicetree/bindings/iio/adc/microchip,pac1944.yaml new file mode 100644 index 000000000000..eca1b25f038b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/microchip,pac1944.yaml @@ -0,0 +1,315 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/microchip,pac1944.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip PAC194x and PAC195x Power Monitors with Accumulator + +maintainers: + - Marius Cristea + +description: | + This device is part of the Microchip family of Power Monitors with + Accumulator. The datasheet for PAC1941-1, PAC1941-2, PAC1942-1, PAC1942-= 2, + PAC1943-1 and PAC1944-1 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/Produc= tDocuments/DataSheets/PAC194X-Family-Data-Sheet-DS20006543.pdf + The datasheet for PAC1951-1, PAC1951-2, PAC1952-1, PAC1952-2, PAC1953-1 = and + PAC1954-1 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/Produc= tDocuments/DataSheets/PAC195X-Family-Data-Sheet-DS20006539.pdf + +properties: + compatible: + enum: + - microchip,pac1941 + - microchip,pac1941-2 + - microchip,pac1942 + - microchip,pac1942-2 + - microchip,pac1943 + - microchip,pac1944 + - microchip,pac1951 + - microchip,pac1951-2 + - microchip,pac1952 + - microchip,pac1952-2 + - microchip,pac1953 + - microchip,pac1954 + + reg: + maxItems: 1 + + vdd-supply: true + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + interrupts: + items: + - description: + ALERT1 output. Asserted when a user-programmed over/under + voltage, current, or power limit routed to ALERT1 is exceeded. + - description: + ALERT2 output. Asserted when a user-programmed over/under + voltage, current, or power limit routed to ALERT2 is exceeded. + minItems: 1 + + interrupt-names: + oneOf: + - items: + - const: alert1 + - items: + - const: alert2 + - items: + - const: alert1 + - const: alert2 + + gpio-controller: true + + "#gpio-cells": + const: 2 + description: | + GPIO 0 corresponds to the SLOW/ALERT1/GPIO multi-function pin and GP= IO 1 + corresponds to the ALERT2/GPIO multi-function pin. The second cell t= akes + standard GPIO flags. + + powerdown-gpios: + description: | + A GPIO used to trigger a change in sampling rate (lowering the chip + power consumption). Driving this GPIO low puts the device in Power-D= own + state (all circuitry is powered down, including SMBus). It should be + marked GPIO_ACTIVE_LOW. + maxItems: 1 + + slow-gpios: + description: | + SLOW input pin. If this pin is forced high, the sampling rate is for= ced + to 8 SPS. When it is forced low, the sampling rate is 1024 SPS, unle= ss + a different sample rate has been programmed. This pin is multi-funct= ion: + SLOW input, ALERT1 output, or general-purpose (open-drain) GPIO; when + used as slow-gpios the ALERT1 function is not available. It should be + marked GPIO_ACTIVE_HIGH. + maxItems: 1 + +patternProperties: + "^channel@[1-4]$": + type: object + $ref: adc.yaml + description: + Represents the external channels which are connected to the ADC. + + properties: + reg: + items: + minimum: 1 + maximum: 4 + + shunt-resistor-micro-ohms: + description: + Value in micro Ohms of the shunt resistor connected between + the SENSE+ and SENSE- inputs, across which the current is measur= ed. + Value is needed to compute the scaling of the measured current. + minimum: 1 + + microchip,vbus-input-range-microvolt: + description: | + In order to increase measurement resolution while keeping the sa= me + number of bits, the device has a configurable VBUS full-scale ra= nge + (FSR). The range should be set by hardware design and it should = not + be changed during runtime. The bipolar capability for VBUS enabl= es + accurate offset measurement and correction. + The VBUS could be configured into the following full-scale range: + - VBUS has unipolar 0V to 32V FSR (default) for PAC195X or 0V = to 9V + (default) for PAC194X. + - VBUS has bipolar -32V to 32V FSR for PAC195X or -9V to 9V for + PAC194X. The actual range is limited to about -200 mV due to= the + impact of the ESD structures. + - VBUS has bipolar -16V to 16V FSR for PAC195X or -4.5V to 4.5= V for + PAC194X. The actual range is limited to about -200 mV due to= the + impact of the ESD structures. + + microchip,vsense-input-range-microvolt: + description: | + In order to decrease the power dissipation on the shunt resistor + and, at the same time, to increase measurement resolution while + keeping the same number of bits, the device has a configurable + VSENSE full-scale scale (FSR). The range should be set by hardwa= re + design and it should not be changed during runtime. + The VSENSE could be configured into the following full-scale ran= ge: + - VSENSE has unipolar 0V to 100 mV FSR (default) + - VSENSE has bipolar -100 mV to 100 mV FSR + - VSENSE has bipolar -50 mV to 50 mV FSR + oneOf: + - items: + - const: 0 + - const: 100000 + - items: + - const: -50000 + - const: 50000 + - items: + - const: -100000 + - const: 100000 + + microchip,accumulation-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + The Hardware Accumulator may be used to accumulate VPOWER or VSE= NSE + values for any channel. By setting the accumulator for a channel + to accumulate the VPOWER values gives a measure of accumulated p= ower + over a time period, which is equivalent to energy. Setting the + accumulator for a channel to accumulate VSENSE values gives a me= asure + of accumulated current, which is equivalent to charge. This allo= ws the + accumulator to be used as a coulomb counter. + This functionality needs to be setup once and must not be changed + during the runtime, just in case the user wants to measure the c= harge + or the energy consumed from board power up till the user has con= trol + or during a reboot of the system. + The Hardware Accumulator could be configured to accumulate VPOWER + or VSENSE + <0> - Accumulator accumulates VPOWER (default) + <1> - Accumulator accumulates VSENSE + enum: [0, 1] + default: 0 + + required: + - reg + - shunt-resistor-micro-ohms + + unevaluatedProperties: false + +required: + - compatible + - reg + - vdd-supply + - "#address-cells" + - "#size-cells" + +allOf: + - if: + properties: + compatible: + pattern: "^microchip,pac194" + then: + patternProperties: + "^channel@[1-4]$": + properties: + microchip,vbus-input-range-microvolt: + oneOf: + - items: + - const: 0 + - const: 9000000 + - items: + - const: -9000000 + - const: 9000000 + - items: + - const: -4500000 + - const: 4500000 + default: [0, 9000000] + - if: + properties: + compatible: + pattern: "^microchip,pac195" + then: + patternProperties: + "^channel@[1-4]$": + properties: + microchip,vbus-input-range-microvolt: + oneOf: + - items: + - const: 0 + - const: 32000000 + - items: + - const: -32000000 + - const: 32000000 + - items: + - const: -16000000 + - const: 16000000 + default: [0, 32000000] + - if: + properties: + compatible: + pattern: "^microchip,pac19[45]1(-2)?$" + then: + patternProperties: + "^channel@[2-4]$": false + - if: + properties: + compatible: + pattern: "^microchip,pac19[45]2(-2)?$" + then: + patternProperties: + "^channel@[3-4]$": false + - if: + properties: + compatible: + pattern: "^microchip,pac19[45]3(-2)?$" + then: + properties: + channel@4: false + - if: + required: + - slow-gpios + then: + properties: + interrupts: + maxItems: 1 + interrupt-names: + oneOf: + - items: + - const: alert2 + - if: + properties: + interrupt-names: + contains: + const: alert1 + required: + - interrupt-names + then: + properties: + slow-gpios: false + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + power-monitor@10 { + compatible =3D "microchip,pac1954"; + reg =3D <0x10>; + vdd-supply =3D <&vdd>; + + #address-cells =3D <1>; + #size-cells =3D <0>; + + channel@1 { + reg =3D <0x1>; + shunt-resistor-micro-ohms =3D <24900>; + label =3D "CPU"; + microchip,vbus-input-range-microvolt =3D <0 32000000>; + microchip,vsense-input-range-microvolt =3D <(-50000) 50000= >; + }; + + channel@3 { + reg =3D <0x3>; + shunt-resistor-micro-ohms =3D <75000>; + label =3D "MEM"; + microchip,vbus-input-range-microvolt =3D <(-16000000) 1600= 0000>; + microchip,vsense-input-range-microvolt =3D <0 100000>; + }; + + channel@4 { + reg =3D <0x4>; + shunt-resistor-micro-ohms =3D <100000>; + label =3D "NET"; + microchip,vbus-input-range-microvolt =3D <(-32000000) 3200= 0000>; + microchip,vsense-input-range-microvolt =3D <(-100000) 1000= 00>; + microchip,accumulation-mode =3D <1>; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 48fda1f8332e..a642d095ad34 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17385,6 +17385,12 @@ S: Supported F: Documentation/devicetree/bindings/iio/adc/microchip,pac1934.yaml F: drivers/iio/adc/pac1934.c =20 +MICROCHIP PAC1944 ADC DRIVER +M: Marius Cristea +L: linux-iio@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/adc/microchip,pac1944.yaml + MICROCHIP PCI1XXXX GP DRIVER M: Vaibhaav Ram T.L M: Kumaravel Thiagarajan --=20 2.51.0 From nobody Fri Jun 12 11:28:12 2026 Received: from esa.microchip.iphmx.com (esa.microchip.iphmx.com [68.232.154.123]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 80B3F43CED6; Fri, 15 May 2026 09:30:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=68.232.154.123 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778837410; cv=none; b=QbanH75XIJ2kjCOEfqi0Yz2g7xrGYNJK1QDBBqsBR2j6wwyJdAXY3eDI3t48PecePhgwT07cRwkIfT/8JRvAI/0Sjy9FBbiaJAFQvIAdD28yeRujEYOWlB25kqTKtxn6i+LolmO0XLQuCAtmJGSYEgN+icDO5JBxoaRXWcCdySY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778837410; c=relaxed/simple; bh=i5dIzsXElsNmPEwouqT929u8hQkqveqsFDPCh0s57yQ=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Im1vJn2gQV4vWaB2KwwFRnCFr184F1Hgfg9a2pj7GQQ5Igt15i5lzcz7JDKsL7zm1oy6V6kA/6tKE0oOsutkfxx8ZFQ50QMT8WEm+5oekOFZpN888CczXV6d2nt+/bb3Zn8y+dHOzFLc7WLzdTI9W+KJpxheHGdrRN3npFJf8hs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microchip.com; spf=pass smtp.mailfrom=microchip.com; dkim=pass (2048-bit key) header.d=microchip.com header.i=@microchip.com header.b=P09c6PAZ; arc=none smtp.client-ip=68.232.154.123 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microchip.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=microchip.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=microchip.com header.i=@microchip.com header.b="P09c6PAZ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=microchip.com; i=@microchip.com; q=dns/txt; s=mchp; t=1778837406; x=1810373406; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=i5dIzsXElsNmPEwouqT929u8hQkqveqsFDPCh0s57yQ=; b=P09c6PAZIfCA00m8AqFx45ZtXEB+Ue02RTvGNvXSSTOktt9KE9ZEfkFQ irmCYpdTbuGT98mT7pw4SuGgJozEE65Oo/6nTYZZBz5ERI3CbyC/TSNsR IQNIAfxxsCF82ctTdjDxflZoM1f6f1k1kjN9oKCnhhT8UcECJ/IHgOMvn lTT5sJlV7pX/lu1PVMy8EN5jwR2nFLcY26hH8zL7kTHRtTmiPSPKwveWq lJCWPGZTOxMO1U7nJ89np3hpZHtXKvnC5G8uv2gkzwvH6HZ59z4zrS8DV UMwsrFgG4s+LTERixgdlUarf2pgH3q7o2mYn0ZHH9+cs9LTqqA1S4GZYu A==; X-CSE-ConnectionGUID: iD09aQL0Td+2XQX0InX/mg== X-CSE-MsgGUID: UF9lb37WTWypocrDKn8AXw== X-IronPort-AV: E=Sophos;i="6.23,236,1770620400"; d="scan'208";a="224794195" X-Amp-Result: SKIPPED(no attachment in message) Received: from unknown (HELO email.microchip.com) ([170.129.1.10]) by esa6.microchip.iphmx.com with ESMTP/TLS/ECDHE-RSA-AES128-GCM-SHA256; 15 May 2026 02:30:05 -0700 Received: from chn-vm-ex03.mchp-main.com (10.10.85.151) by chn-vm-ex03.mchp-main.com (10.10.85.151) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.58; Fri, 15 May 2026 02:30:05 -0700 Received: from marius-VM.mshome.net (10.10.85.11) by chn-vm-ex03.mchp-main.com (10.10.85.151) with Microsoft SMTP Server id 15.1.2507.58 via Frontend Transport; Fri, 15 May 2026 02:30:02 -0700 From: To: , , , CC: , , , , , , , Subject: [PATCH v4 2/2] iio: adc: add support for PAC194X Date: Fri, 15 May 2026 12:29:46 +0300 Message-ID: <20260515092946.10791-3-marius.cristea@microchip.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260515092946.10791-1-marius.cristea@microchip.com> References: <20260515092946.10791-1-marius.cristea@microchip.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Marius Cristea This is the iio driver for Microchip PAC194X and PAC195X series of Power Monitors with Accumulator. The PAC194X family supports 9V Full-Scale Range and the PAC195X supports 32V Full-Scale Range. There are two versions of the PAC194X/5X: the PAC194X/5X-1 devices are for high-side current sensing and the PAC194X/5X-2 devices are for low-side current sensing or floating VBUS applications. The PAC194X/5X-1 is named shortly PAC194X/5X. Signed-off-by: Marius Cristea --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/pac1944.c | 2172 +++++++++++++++++++++++++++++++++++++ 4 files changed, 2186 insertions(+) create mode 100644 drivers/iio/adc/pac1944.c diff --git a/MAINTAINERS b/MAINTAINERS index a642d095ad34..6e4221e5f446 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17390,6 +17390,7 @@ M: Marius Cristea L: linux-iio@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/iio/adc/microchip,pac1944.yaml +F: drivers/iio/adc/pac1944.c =20 MICROCHIP PCI1XXXX GP DRIVER M: Vaibhaav Ram T.L diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index a9dedbb8eb46..8370b82c7b56 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1290,6 +1290,18 @@ config PAC1934 This driver can also be built as a module. If so, the module will be called pac1934. =20 +config PAC1944 + tristate "Microchip Technology PAC1944/PAC1954 driver" + depends on I2C + help + Say yes here to build support for Microchip Technology's PAC1941, + PAC1941-2, PAC1942, PAC1942-2, PAC1943, PAC1944, PAC1951, + PAC1951-2, PAC1952, PAC1952-2, PAC1953, PAC1954 + Single/Multi-Channel Power Monitor with Accumulator. + + This driver can also be built as a module. If so, the module + will be called pac1944. + config PALMAS_GPADC tristate "TI Palmas General Purpose ADC" depends on MFD_PALMAS diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 097357d146ba..0e1cc47b68a1 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -113,6 +113,7 @@ obj-$(CONFIG_NPCM_ADC) +=3D npcm_adc.o obj-$(CONFIG_NXP_SAR_ADC) +=3D nxp-sar-adc.o obj-$(CONFIG_PAC1921) +=3D pac1921.o obj-$(CONFIG_PAC1934) +=3D pac1934.o +obj-$(CONFIG_PAC1934) +=3D pac1944.o obj-$(CONFIG_PALMAS_GPADC) +=3D palmas_gpadc.o obj-$(CONFIG_QCOM_PM8XXX_XOADC) +=3D qcom-pm8xxx-xoadc.o obj-$(CONFIG_QCOM_SPMI_ADC5) +=3D qcom-spmi-adc5.o diff --git a/drivers/iio/adc/pac1944.c b/drivers/iio/adc/pac1944.c new file mode 100644 index 000000000000..3938bbcd7e6b --- /dev/null +++ b/drivers/iio/adc/pac1944.c @@ -0,0 +1,2172 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for PAC194X and PAC195X series chips + * + * Copyright (C) 2022-2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Marius Cristea marius.cristea@microchip.com + * + * Datasheet for PAC1941, PAC1942, PAC1943 and PAC1944 can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/Product= Documents/DataSheets/PAC194X-Family-Data-Sheet-DS20006543.pdf + * Datasheet for PAC1951, PAC1952, PAC1953 and PAC1954 can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/Product= Documents/DataSheets/PAC195X-Family-Data-Sheet-DS20006539.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Maximum (1092 * 60 * 1000), around 1092 minutes@1024 sps + * We will keep the refresh lower + */ +#define PAC1944_MAX_RFSH_LIMIT_MS 300000 + +/* 50msec is the timeout for validity of the cached registers */ +#define PAC1944_MIN_POLLING_TIME_MS 50 +/* + * 1000usec is the minimum wait time for normal conversions when sample + * rate doesn't change + */ +#define PAC1944_MIN_UPDATE_WAIT_TIME_US 1000 + +#define PAC1944_SHUNT_UOHMS_DEFAULT 100000 + +/* 32000mV */ +#define PAC195X_VOLTAGE_MILLIVOLTS_MAX 32000 +/* 9000mV */ +#define PAC194X_VOLTAGE_MILLIVOLTS_MAX 9000 + +/* + * Voltage bits resolution when set for unsigned values and + * HALF FSR signed values + */ +#define PAC1944_VOLTAGE_16B_RES 16 +/* Voltage bits resolution when set for signed values */ +#define PAC1944_VOLTAGE_15B_RES 15 + +/* 100mV maximum voltage drop over the sense resistors */ +#define PAC1944_VSENSE_MILLIVOLTS_MAX 100 + +#define PAC1944_VSENSE_MICROVOLTS_MAX 100000 +#define PAC1944_VSENSE_MICROVOLTS_MIN (-100000) + +/* + * Current bits resolution when set for unsigned values and + * HALF FSR signed values + */ +#define PAC1944_CURRENT_16B_RES 16 + +/* Current bits resolution when set for signed values */ +#define PAC1944_CURRENT_15B_RES 15 + +/* Power resolution is 30 bits when unsigned and HALF FSR signed values */ +#define PAC1944_POWER_30B_RES 30 + +/* Power resolution is 29 bits when signed */ +#define PAC1944_POWER_29B_RES 29 + +/* Accumulation register is 56 bits long for unipolar range */ +#define PAC1944_ENERGY_56B_RES 56 + +/* Accumulation register is 56 bits long for bipolar range */ +#define PAC1944_ENERGY_55B_RES 55 + +/* Maximum power-product value - 32 V * 0.1 V */ +#define PAC195X_PRODUCT_VOLTAGE_PV_FSR 3200000000000UL + +/* Maximum power-product value - 9 V * 0.1 V */ +#define PAC194X_PRODUCT_VOLTAGE_PV_FSR 900000000000UL + +#define PAC1944_MEAS_REG_SNAPSHOT_LEN 80 +#define PAC1944_CTRL_REG_SNAPSHOT_LEN 24 + +#define PAC1944_DEFAULT_CHIP_SAMP_SPEED_HZ 1024 + +/* Device register address map */ +#define PAC1944_REFRESH_REG_ADDR 0x00 +#define PAC1944_CTRL_REG_ADDR 0x01 +#define PAC1944_ACC_COUNT_REG_ADDR 0x02 +#define PAC1944_VACC_1_REG_ADDR 0x03 +#define PAC1944_VACC_2_REG_ADDR 0x04 +#define PAC1944_VACC_3_REG_ADDR 0x05 +#define PAC1944_VACC_4_REG_ADDR 0x06 +#define PAC1944_VBUS_1_ADDR 0x07 +#define PAC1944_VBUS_2_ADDR 0x08 +#define PAC1944_VBUS_3_ADDR 0x09 +#define PAC1944_VBUS_4_ADDR 0x0A +#define PAC1944_VSENSE_1_ADDR 0x0B +#define PAC1944_VSENSE_2_ADDR 0x0C +#define PAC1944_VSENSE_3_ADDR 0x0D +#define PAC1944_VSENSE_4_ADDR 0x0E +#define PAC1944_VBUS_AVG_1_ADDR 0x0F +#define PAC1944_VBUS_AVG_2_ADDR 0x10 +#define PAC1944_VBUS_AVG_3_ADDR 0x11 +#define PAC1944_VBUS_AVG_4_ADDR 0x12 +#define PAC1944_VSENSE_AVG_1_ADDR 0x13 +#define PAC1944_VSENSE_AVG_2_ADDR 0x14 +#define PAC1944_VSENSE_AVG_3_ADDR 0x15 +#define PAC1944_VSENSE_AVG_4_ADDR 0x16 +#define PAC1944_VPOWER_1_ADDR 0x17 +#define PAC1944_VPOWER_2_ADDR 0x18 +#define PAC1944_VPOWER_3_ADDR 0x19 +#define PAC1944_VPOWER_4_ADDR 0x1A + +/* Start of configurations registers */ +#define PAC1944_SMBUS_SETTINGS_REGS_ADDR 0x1C +#define PAC1944_NEG_PWR_FSR_REG_ADDR 0x1D +#define PAC1944_REFRESH_G_REG_ADDR 0x1E +#define PAC1944_REFRESH_V_REG_ADDR 0x1F +#define PAC1944_SLOW_REG_ADDR 0x20 +#define PAC1944_CTRL_ACT_REG_ADDR 0x21 +#define PAC1944_CTRL_LAT_REG_ADDR 0x23 +#define PAC1944_NEG_PWR_FSR_LAT_REG_ADDR 0x24 +#define PAC1944_ACCUM_CFG_REG_ADDR 0x25 + +/* + * Registers related to alert functionality + */ +#define PAC1944_ALERT_STATUS_REG_ADDR 0x26 +#define PAC1944_SLOW_ALERT1_REG_ADDR 0x27 +#define PAC1944_GPIO_ALERT2_REG_ADDR 0x28 +#define PAC1944_ACC_FULLNESS_LIMITS_REG_ADDR 0x29 +#define PAC1944_OC_LIMIT_REG_ADDR 0x30 +#define PAC1944_UC_LIMIT_REG_ADDR 0x34 +#define PAC1944_OP_LIMIT_REG_ADDR 0x38 +#define PAC1944_OV_LIMIT_REG_ADDR 0x3C +#define PAC1944_UV_LIMIT_REG_ADDR 0x40 +#define PAC1944_OC_LIMIT_NSAMPLES_REG_ADDR 0x44 +#define PAC1944_UC_LIMIT_NSAMPLES_REG_ADDR 0x45 +#define PAC1944_OP_LIMIT_NSAMPLES_REG_ADDR 0x46 +#define PAC1944_OV_LIMIT_NSAMPLES_REG_ADDR 0x47 +#define PAC1944_UV_LIMIT_NSAMPLES_REG_ADDR 0x48 +#define PAC1944_ALERT_ENABLE_REG_ADDR 0x49 + +#define PAC1944_ALERT_ENABLE_REG_LEN 3 +#define PAC1944_ALERTS_REG_LEN 63 + +#define PAC1944_PID_REG_ADDR 0xFD + +/* Alert Enable register */ +#define PAC1944_OC_MASK GENMASK(23, 20) +#define PAC1944_CH01OC_MASK BIT(23) +#define PAC1944_CH01OC_SET BIT(23) +#define PAC1944_CH02OC_MASK BIT(22) +#define PAC1944_CH02OC_SET BIT(22) +#define PAC1944_CH03OC_MASK BIT(21) +#define PAC1944_CH03OC_SET BIT(21) +#define PAC1944_CH04OC_MASK BIT(20) +#define PAC1944_CH04OC_SET BIT(20) + +#define PAC1944_UC_MASK GENMASK(19, 16) +#define PAC1944_CH01UC_MASK BIT(19) +#define PAC1944_CH01UC_SET BIT(19) +#define PAC1944_CH02UC_MASK BIT(18) +#define PAC1944_CH02UC_SET BIT(18) +#define PAC1944_CH03UC_MASK BIT(17) +#define PAC1944_CH03UC_SET BIT(17) +#define PAC1944_CH04UC_MASK BIT(16) +#define PAC1944_CH04UC_SET BIT(16) + +#define PAC1944_OV_MASK GENMASK(15, 12) +#define PAC1944_CH01OV_MASK BIT(15) +#define PAC1944_CH01OV_SET BIT(15) +#define PAC1944_CH02OV_MASK BIT(14) +#define PAC1944_CH02OV_SET BIT(14) +#define PAC1944_CH03OV_MASK BIT(13) +#define PAC1944_CH03OV_SET BIT(13) +#define PAC1944_CH04OV_MASK BIT(12) +#define PAC1944_CH04OV_SET BIT(12) + +#define PAC1944_UV_MASK GENMASK(11, 8) +#define PAC1944_CH01UV_MASK BIT(11) +#define PAC1944_CH01UV_SET BIT(11) +#define PAC1944_CH02UV_MASK BIT(10) +#define PAC1944_CH02UV_SET BIT(10) +#define PAC1944_CH03UV_MASK BIT(9) +#define PAC1944_CH03UV_SET BIT(9) +#define PAC1944_CH04UV_MASK BIT(8) +#define PAC1944_CH04UV_SET BIT(8) + +#define PAC1944_OP_MASK GENMASK(7, 4) +#define PAC1944_CH01OP_MASK BIT(7) +#define PAC1944_CH01OP_SET BIT(7) +#define PAC1944_CH02OP_MASK BIT(6) +#define PAC1944_CH02OP_SET BIT(6) +#define PAC1944_CH03OP_MASK BIT(5) +#define PAC1944_CH03OP_SET BIT(5) +#define PAC1944_CH04OP_MASK BIT(4) +#define PAC1944_CH04OP_SET BIT(4) + +#define PAC1944_ACC_OVF_MASK BIT(3) +#define PAC1944_ACC_OVF_SET BIT(3) + +#define PAC1944_ACC_COUNT_MASK BIT(2) +#define PAC1944_ACC_COUNT_SET BIT(2) + +#define PAC1944_ALERT_CC1_MASK BIT(1) +#define PAC1944_ALERT_CC1_SET BIT(1) + +#define PAC1944_ACC_REG_LEN 4 +#define PAC1944_VACC_REG_LEN 7 +#define PAC1944_VBUS_SENSE_REG_LEN 2 +#define PAC1944_VPOWER_REG_LEN 4 +#define PAC1944_CTRL_ACT_REG_LEN 2 +#define PAC1944_CTRL_LAT_REG_LEN 2 +#define PAC1944_MAX_REGISTER_LEN 6 + +#define PAC1944_COMMON_DEVATTR 1 +#define PAC1944_ACC_DEVATTR 3 + +#define PAC1944_MAX_CH 4 + +/* PAC194X family */ +#define PAC_PRODUCT_ID_1941 0x68 +#define PAC_PRODUCT_ID_1942 0x69 +#define PAC_PRODUCT_ID_1943 0x6A +#define PAC_PRODUCT_ID_1944 0x6B +#define PAC_PRODUCT_ID_1941_2 0x6C +#define PAC_PRODUCT_ID_1942_2 0x6D +/* PAC195x family */ +#define PAC_PRODUCT_ID_1951 0x78 +#define PAC_PRODUCT_ID_1952 0x79 +#define PAC_PRODUCT_ID_1953 0x7A +#define PAC_PRODUCT_ID_1954 0x7B +#define PAC_PRODUCT_ID_1951_2 0x7C +#define PAC_PRODUCT_ID_1952_2 0x7D + +#define PAC1944_ALERT 0x00 +#define PAC1944_GPIO_INPUT 0x01 +#define PAC1944_GPIO_OUTPUT 0x02 +#define PAC1944_SLOW 0x03 + +#define PAC1944_CTRL_SAMPLE_MASK GENMASK(15, 12) +#define PAC1944_CTRL_GPIO_ALERT2_MASK GENMASK(11, 10) +#define PAC1944_CTRL_SLOW_ALERT1_MASK GENMASK(9, 8) +#define PAC1944_CTRL_CH_1_OFF_MASK BIT(7) +#define PAC1944_CTRL_CH_2_OFF_MASK BIT(6) +#define PAC1944_CTRL_CH_3_OFF_MASK BIT(5) +#define PAC1944_CTRL_CH_4_OFF_MASK BIT(4) + +#define PAC1944_NEG_PWR_CFG_VS1_MASK GENMASK(15, 14) +#define PAC1944_NEG_PWR_CFG_VS2_MASK GENMASK(13, 12) +#define PAC1944_NEG_PWR_CFG_VS3_MASK GENMASK(11, 10) +#define PAC1944_NEG_PWR_CFG_VS4_MASK GENMASK(9, 8) +#define PAC1944_NEG_PWR_CFG_VB1_MASK GENMASK(7, 6) +#define PAC1944_NEG_PWR_CFG_VB2_MASK GENMASK(5, 4) +#define PAC1944_NEG_PWR_CFG_VB3_MASK GENMASK(3, 2) +#define PAC1944_NEG_PWR_CFG_VB4_MASK GENMASK(1, 0) + +#define PAC1944_CFG_ACC4_SHIFT 0 +#define PAC1944_CFG_ACC3_SHIFT 2 +#define PAC1944_CFG_ACC2_SHIFT 4 +#define PAC1944_CFG_ACC1_SHIFT 6 + +#define PAC1944_ACPI_GET_NAMES 1 +#define PAC1944_ACPI_GET_UOHMS_VALS 2 +#define PAC1944_ACPI_GET_BIPOLAR_SETTINGS 4 + +#define ACCUM_REG(acc1_cfg, acc2_cfg, acc3_cfg, acc4_cfg) \ + ((((acc1_cfg) & 0x03) << PAC1944_CFG_ACC1_SHIFT) | \ + (((acc2_cfg) & 0x03) << PAC1944_CFG_ACC2_SHIFT) | \ + (((acc3_cfg) & 0x03) << PAC1944_CFG_ACC3_SHIFT) | \ + (((acc4_cfg) & 0x03) << PAC1944_CFG_ACC4_SHIFT)) + +/* + * Accumulated power/energy formula (in mW-seconds): + * Energy =3D (Vacc/10^9)*[(10^9/2^30)*2^9]*3.2*10^3/Rsense + * Vacc - is the accumulated value per second + * Rsense - value of the shunt resistor in microOhms + * + * PAC195X_MAX_VPOWER_RSHIFTED_BY_29B =3D 3.2*((10^9)/(2^29))*10^9 + * will be used to calculate the scale for accumulated power/energy + */ +#define PAC195X_MAX_VPOWER_RSHIFTED_BY_29B 5960464478UL + +/* + * PAC194X_MAX_VPOWER_RSHIFTED_BY_29B =3D 0.9*((10^9)/(2^29))*10^9 + * will be used to calculate the scale for accumulated power/energy + */ +#define PAC194X_MAX_VPOWER_RSHIFTED_BY_29B 1676380634UL + +/* (100mV * 1000000) / (2^15) used to calculate the scale for current */ +#define PAC1944_MAX_VSENSE_RSHIFTED_BY_15B 3052 + +/* + * [(100mV * 1000000) / (2^15)]*10^9 used to calculate the scale + * for accumulated current/Coulomb counter + */ +#define PAC1944_MAX_VSENSE_NANO 3051757812500UL + +#define to_pac1944_chip_info(d) container_of(d, struct pac1944_chip_info, = work_chip_rfsh) + +/* + * these indexes are exactly describing the element order within a single + * PAC1944/54 phys channel IIO channel descriptor; see the static const st= ruct + * iio_chan_spec pac1944_single_channel[] declaration + */ +enum pac1944_ch_idx { + PAC1944_CH_POWER, + PAC1944_CH_VOLTAGE, + PAC1944_CH_CURRENT, + PAC1944_CH_VOLTAGE_AVERAGE, + PAC1944_CH_CURRENT_AVERAGE, +}; + +enum pac1944_acc_mode { + PAC1944_ACCMODE_VPOWER, + PAC1944_ACCMODE_VSENSE, +}; + +enum pac1944_vbus_fsr_cfg { + PAC1944_UNIPOLAR_FSR_CFG, + PAC1944_BIPOLAR_FSR_CFG, + PAC1944_BIPOLAR_HALF_FSR_CFG, +}; + +enum pac1944_samps { + PAC1944_SAMP_1024SPS, + PAC1944_SAMP_256SPS, + PAC1944_SAMP_64SPS, + PAC1944_SAMP_8SPS, +}; + +enum pac1944_adc_range { + PAC1944_ADC_FULL_RANGE_BIPOLAR, + PAC1944_ADC_HALF_RANGE_BIPOLAR, + PAC1944_ADC_FULL_RANGE_UNIPOLAR, +}; + +/* + * The PAC195X has a feature called Adaptive Accumulator mode (APAPT). In = this + * mode, sampling is programmed at one of the valid sample rates and sampl= es are + * accumulated. If the SLOW pin is asserted and the device begins sampling= at + * 8 SPS, these samples are shifted by 7 bits to the left and accumulated = so as + * to simulate sampling at the maximum sampling rate, 1024 SPS, and the + * accumulator count is also incremented by 128 for each sample in Slow mo= de + * (when using the Adaptive Accumulator mode) to simulate samples being + * accumulated at the maximum sampling rate. + * This offers a big reduction in host overhead and bus traffic for system= s that + * need to use the SLOW pin for lower power operation during certain times= and + * want to have continuous accurate energy monitoring for both the maximum + * sampling rate and the SLOW sampling rate. + */ +static const unsigned int pac1944_samp_rate_map_tbl[] =3D { + [PAC1944_SAMP_1024SPS] =3D 1024, + [PAC1944_SAMP_256SPS] =3D 256, + [PAC1944_SAMP_64SPS] =3D 64, + [PAC1944_SAMP_8SPS] =3D 8, +}; + +static const unsigned int shift_map_tbl[] =3D { + [PAC1944_SAMP_1024SPS] =3D 10, + [PAC1944_SAMP_256SPS] =3D 8, + [PAC1944_SAMP_64SPS] =3D 6, + [PAC1944_SAMP_8SPS] =3D 3, +}; + +/* Available Sample Modes */ +static const char * const pac1944_frequency_avail[] =3D { + "1024", + "256", + "64", + "8", +}; + +static const int pac1944_adc_range_tbl[][2] =3D { + [PAC1944_ADC_FULL_RANGE_BIPOLAR] =3D {-9000000, 9000000}, + [PAC1944_ADC_HALF_RANGE_BIPOLAR] =3D {-4500000, 4500000}, + [PAC1944_ADC_FULL_RANGE_UNIPOLAR] =3D {0, 9000000}, +}; + +static const int pac1954_adc_range_tbl[][2] =3D { + [PAC1944_ADC_FULL_RANGE_BIPOLAR] =3D {-32000000, 32000000}, + [PAC1944_ADC_HALF_RANGE_BIPOLAR] =3D {-16000000, 16000000}, + [PAC1944_ADC_FULL_RANGE_UNIPOLAR] =3D {0, 32000000}, +}; + +static const int pac1944_adc_vsense_range_tbl[][2] =3D { + [PAC1944_ADC_FULL_RANGE_BIPOLAR] =3D {-100000, 100000}, + [PAC1944_ADC_HALF_RANGE_BIPOLAR] =3D {-50000, 50000}, + [PAC1944_ADC_FULL_RANGE_UNIPOLAR] =3D {0, 100000}, +}; + +/** + * struct reg_data - data from the registers + * @vsense_mode:array of values, Full Scale Range (FSR) mode for V Sense + * @vbus_mode: array of values, Full Scale Range (FSR) mode for V Bus + * @accumulation_mode: array of values, accumulation mode for hardware acc= umulator + * @meas_regs: snapshot of raw measurements registers + * @ctrl_act_reg: snapshot of the ctrl_act register + * @ctrl_lat_reg: snapshot of the ctrl_lat register + * @acc_count: snapshot of the acc_count register + * @acc_val: accumulated values per second + * @vacc: accumulated vpower values + * @vpower: snapshot of vpower registers + * @vbus: snapshot of vbus registers + * @vbus_avg: averages of vbus registers + * @vsense: snapshot of vsense registers + * @vsense_avg: averages of vsense registers + * @jiffies_tstamp: chip's uptime + */ +struct reg_data { + u8 vbus_mode[PAC1944_MAX_CH]; + u8 vsense_mode[PAC1944_MAX_CH]; + u8 accumulation_mode[PAC1944_MAX_CH]; + u8 meas_regs[PAC1944_MEAS_REG_SNAPSHOT_LEN]; + u16 ctrl_act_reg; + u16 ctrl_lat_reg; + u32 acc_count; + s64 acc_val[PAC1944_MAX_CH]; + s64 vacc[PAC1944_MAX_CH]; + s32 vpower[PAC1944_MAX_CH]; + s32 vbus[PAC1944_MAX_CH]; + s32 vbus_avg[PAC1944_MAX_CH]; + s32 vsense[PAC1944_MAX_CH]; + s32 vsense_avg[PAC1944_MAX_CH]; + unsigned long jiffies_tstamp; +}; + +/** + * struct pac1944_chip_info - chip configuration + * @channels: array of values, true means that channel is active + * @iio_info: pointer to iio_info structure + * @client: a pointer to the i2c client associated with the device + * @lock: lock to prevent concurrent reads/writes + * @work_chip_rfsh: chip refresh workqueue implementation + * @active_channels_mask: active channels + * @phys_channels: number of physical channels for the device + * @shunts: array of values, shunt resistor values + * @chip_reg_data: pointer to structure, containing data from the device r= egisters + * @labels: array of string, name of each channel + * @is_pac195x_family: true if device is part of the PAC195x family + * @sampling_mode: sampling mode used by the device + * @num_enabled_channels: count of how many chip channels are currently en= abled + * @enable_acc: array of values, true means that accumulation channel is m= easured + */ +struct pac1944_chip_info { + const struct iio_chan_spec *channels; + struct iio_info iio_info; + struct i2c_client *client; + struct mutex lock; /* lock to prevent concurrent reads/writes */ + struct delayed_work work_chip_rfsh; + unsigned long active_channels_mask; + u8 phys_channels; + u32 shunts[PAC1944_MAX_CH]; + struct reg_data chip_reg_data; + char *labels[PAC1944_MAX_CH]; + bool is_pac195x_family; + u8 sampling_mode; + u8 num_enabled_channels; + bool enable_acc[PAC1944_MAX_CH]; +}; + +/** + * struct pac1944_features - features of a pac194x instance + * @phys_channels: number of physical channels supported by the chip + * @prod_id: hardware ID + * @name: chip's name + */ +struct pac1944_features { + u8 phys_channels; + u8 prod_id; + const char *name; +}; + +/* PAC194X Family */ +static const struct pac1944_features pac1941_chip_config =3D { + .phys_channels =3D 1, + .prod_id =3D PAC_PRODUCT_ID_1941, + .name =3D "pac1941", +}; + +static const struct pac1944_features pac1942_chip_config =3D { + .phys_channels =3D 2, + .prod_id =3D PAC_PRODUCT_ID_1942, + .name =3D "pac1942", +}; + +static const struct pac1944_features pac1943_chip_config =3D { + .phys_channels =3D 3, + .prod_id =3D PAC_PRODUCT_ID_1943, + .name =3D "pac1943", +}; + +static const struct pac1944_features pac1944_chip_config =3D { + .phys_channels =3D 4, + .prod_id =3D PAC_PRODUCT_ID_1944, + .name =3D "pac1944", +}; + +static const struct pac1944_features pac1941_2_chip_config =3D { + .phys_channels =3D 1, + .prod_id =3D PAC_PRODUCT_ID_1941_2, + .name =3D "pac1941_2", +}; + +static const struct pac1944_features pac1942_2_chip_config =3D { + .phys_channels =3D 2, + .prod_id =3D PAC_PRODUCT_ID_1942_2, + .name =3D "pac1942_2", +}; + +/* PAC195X Family */ +static const struct pac1944_features pac1951_chip_config =3D { + .phys_channels =3D 1, + .prod_id =3D PAC_PRODUCT_ID_1951, + .name =3D "pac1951", +}; + +static const struct pac1944_features pac1952_chip_config =3D { + .phys_channels =3D 2, + .prod_id =3D PAC_PRODUCT_ID_1952, + .name =3D "pac1952", +}; + +static const struct pac1944_features pac1953_chip_config =3D { + .phys_channels =3D 3, + .prod_id =3D PAC_PRODUCT_ID_1953, + .name =3D "pac1953", +}; + +static const struct pac1944_features pac1954_chip_config =3D { + .phys_channels =3D 4, + .prod_id =3D PAC_PRODUCT_ID_1954, + .name =3D "pac1954", +}; + +static const struct pac1944_features pac1951_2_chip_config =3D { + .phys_channels =3D 1, + .prod_id =3D PAC_PRODUCT_ID_1951_2, + .name =3D "pac1951_2", +}; + +static const struct pac1944_features pac1952_2_chip_config =3D { + .phys_channels =3D 2, + .prod_id =3D PAC_PRODUCT_ID_1952_2, + .name =3D "pac1952_2", +}; + +static inline u64 pac1944_get_unaligned_be56(const u8 *p) +{ + return (u64)p[0] << 48 | (u64)p[1] << 40 | (u64)p[2] << 32 | + (u64)p[3] << 24 | (u64)p[4] << 16 | (u64)p[5] << 8 | (u64)p[6]; +} + +/* Low-level I2c functions used to transfer more then 32 bytes at once */ +static int pac1944_i2c_read(struct i2c_client *client, u8 reg_addr, + void *databuf, u8 len) +{ + struct i2c_msg msgs[2] =3D { + { + .addr =3D client->addr, + .len =3D 1, + .buf =3D (u8 *)®_addr, + .flags =3D 0 + }, + { + .addr =3D client->addr, + .len =3D len, + .buf =3D databuf, + .flags =3D I2C_M_RD + } + }; + + return i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +} + +/* Custom IIO Device Attributes */ +static ssize_t pac1944_shunt_value_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + + return sysfs_emit(buf, "%u\n", info->shunts[this_attr->address]); +} + +static ssize_t pac1944_shunt_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + int sh_val; + + if (kstrtouint(buf, 10, &sh_val)) { + dev_err(dev, "Shunt value is not valid\n"); + return -EINVAL; + } + + scoped_guard(mutex, &info->lock) + info->shunts[this_attr->address] =3D sh_val; + + return count; +} + +static IIO_DEVICE_ATTR(in_current1_shunt_resistor, 0644, + pac1944_shunt_value_show, pac1944_shunt_value_store, 0); +static IIO_DEVICE_ATTR(in_current2_shunt_resistor, 0644, + pac1944_shunt_value_show, pac1944_shunt_value_store, 1); +static IIO_DEVICE_ATTR(in_current3_shunt_resistor, 0644, + pac1944_shunt_value_show, pac1944_shunt_value_store, 2); +static IIO_DEVICE_ATTR(in_current4_shunt_resistor, 0644, + pac1944_shunt_value_show, pac1944_shunt_value_store, 3); + +static struct attribute *pac1944_all_attrs[] =3D { + &iio_dev_attr_in_current1_shunt_resistor.dev_attr.attr, + &iio_dev_attr_in_current2_shunt_resistor.dev_attr.attr, + &iio_dev_attr_in_current3_shunt_resistor.dev_attr.attr, + &iio_dev_attr_in_current4_shunt_resistor.dev_attr.attr, + NULL +}; + +#define PAC1944_VBUS_CHANNEL(_index, _address) { \ + .type =3D IIO_VOLTAGE, \ + .address =3D (_address), \ + .indexed =3D 1, \ + .channel =3D (_index) + 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info =3D pac1944_ext_info \ +} + +#define PAC1944_VBUS_AVG_CHANNEL(_index, _address) { \ + .type =3D IIO_VOLTAGE, \ + .address =3D (_address), \ + .indexed =3D 1, \ + .channel =3D (_index) + 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info =3D pac1944_ext_info \ +} + +#define PAC1944_VSENSE_CHANNEL(_index, _address) { \ + .type =3D IIO_CURRENT, \ + .address =3D (_address), \ + .indexed =3D 1, \ + .channel =3D (_index) + 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info =3D pac1944_ext_info \ +} + +#define PAC1944_VSENSE_AVG_CHANNEL(_index, _address) { \ + .type =3D IIO_CURRENT, \ + .address =3D (_address), \ + .indexed =3D 1, \ + .channel =3D (_index) + 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info =3D pac1944_ext_info \ +} + +#define PAC1944_VPOWER_CHANNEL(_index, _address) { \ + .type =3D IIO_POWER, \ + .address =3D (_address), \ + .indexed =3D 1, \ + .channel =3D (_index) + 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info =3D pac1944_ext_info \ +} + +static int pac1944_send_refresh(struct pac1944_chip_info *info, u8 refresh= _addr, + u32 wait_time) +{ + struct i2c_client *client =3D info->client; + int ret; + + /* Writing a REFRESH or a REFRESH_V command. */ + ret =3D i2c_smbus_write_byte(client, refresh_addr); + if (ret) { + dev_err(&client->dev, "%s - cannot send Refresh cmd (0x%02X) to PAC1944\= n", + __func__, refresh_addr); + return ret; + } + + /* Register data retrieval timestamp. */ + info->chip_reg_data.jiffies_tstamp =3D jiffies; + /* Wait till the data is available. */ + usleep_range(wait_time, wait_time + 100); + + return 0; +} + +static int pac1944_reg_snapshot(struct pac1944_chip_info *info, bool do_re= fresh, + u8 refresh_addr, u32 wait_time) +{ + struct i2c_client *client =3D info->client; + struct device *dev =3D &client->dev; + s64 stored_value, tmp_s64; + u8 *offset_reg_data_p; + bool is_unipolar; + u16 smpl_mode; + int cnt, ret; + s64 inc =3D 0; + u8 shift; + + guard(mutex)(&info->lock); + + if (do_refresh) { + ret =3D pac1944_send_refresh(info, refresh_addr, wait_time); + if (ret < 0) { + dev_err(dev, "cannot send refresh towards PAC1944\n"); + return ret; + } + } + + /* Read the ctrl/status registers for this snapshot. */ + ret =3D i2c_smbus_read_word_swapped(client, PAC1944_CTRL_ACT_REG_ADDR); + if (ret < 0) { + dev_err(dev, "cannot read PAC1944 regs from 0x%02X\n", PAC1944_CTRL_ACT_= REG_ADDR); + return ret; + } + + info->chip_reg_data.ctrl_act_reg =3D ret; + + ret =3D i2c_smbus_read_word_swapped(client, PAC1944_CTRL_LAT_REG_ADDR); + if (ret < 0) { + dev_err(dev, "cannot read PAC1944 regs from 0x%02X\n", PAC1944_CTRL_LAT_= REG_ADDR); + return ret; + } + + info->chip_reg_data.ctrl_lat_reg =3D ret; + + /* Read the data registers. */ + ret =3D pac1944_i2c_read(client, PAC1944_ACC_COUNT_REG_ADDR, + (u8 *)info->chip_reg_data.meas_regs, + PAC1944_MEAS_REG_SNAPSHOT_LEN); + if (ret < 0) { + dev_err(dev, "cannot read PAC1944 regs from 0x%02X\n", PAC1944_ACC_COUNT= _REG_ADDR); + return ret; + } + + offset_reg_data_p =3D &info->chip_reg_data.meas_regs[0]; + + info->chip_reg_data.acc_count =3D get_unaligned_be32(offset_reg_data_p); + + offset_reg_data_p +=3D PAC1944_ACC_REG_LEN; + + /* + * Check if the channel is active (within the data read from the chip), + * skip all fields if disabled. + */ + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + /* skip if the energy accumulation is disabled */ + if (!info->enable_acc[cnt]) { + offset_reg_data_p +=3D PAC1944_VACC_REG_LEN; + continue; + } + + stored_value =3D info->chip_reg_data.acc_val[cnt]; + + info->chip_reg_data.vacc[cnt] =3D pac1944_get_unaligned_be56(offset_reg_= data_p); + + is_unipolar =3D true; + switch (info->chip_reg_data.accumulation_mode[cnt]) { + case PAC1944_ACCMODE_VPOWER: + if (info->chip_reg_data.vbus_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG || + info->chip_reg_data.vsense_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + is_unipolar =3D false; + break; + case PAC1944_ACCMODE_VSENSE: + if (info->chip_reg_data.vsense_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + is_unipolar =3D false; + break; + default: + return -EINVAL; + } + + if (!is_unipolar) + info->chip_reg_data.vacc[cnt] =3D + sign_extend64(info->chip_reg_data.vacc[cnt], 55); + + /* + * Integrate the accumulated power or current over the elapsed + * interval. + */ + smpl_mode =3D FIELD_GET(PAC1944_CTRL_SAMPLE_MASK, info->chip_reg_data.ct= rl_lat_reg); + + if (smpl_mode <=3D PAC1944_SAMP_8SPS) { + tmp_s64 =3D info->chip_reg_data.vacc[cnt]; + /* + * Find how much shift is required by the sample rate. + * The chip's sampling rate is 2^shift samples/sec. + */ + shift =3D shift_map_tbl[smpl_mode]; + inc =3D tmp_s64 >> shift; + } else { + dev_err(dev, "Invalid sample rate index: %d!\n", smpl_mode); + return -EINVAL; + } + + if (check_add_overflow(stored_value, inc, &stored_value)) { + if (stored_value < 0) + info->chip_reg_data.acc_val[cnt] =3D S64_MIN; + else + info->chip_reg_data.acc_val[cnt] =3D S64_MAX; + + dev_err(dev, "Overflow detected on channel [%d]!\n", cnt + 1); + } else { + info->chip_reg_data.acc_val[cnt] =3D stored_value; + } + + offset_reg_data_p +=3D PAC1944_VACC_REG_LEN; + } + + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + info->chip_reg_data.vbus[cnt] =3D get_unaligned_be16(offset_reg_data_p); + + if (info->chip_reg_data.vbus_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + info->chip_reg_data.vbus[cnt] =3D + sign_extend32(info->chip_reg_data.vbus[cnt], 15); + + offset_reg_data_p +=3D PAC1944_VBUS_SENSE_REG_LEN; + } + + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + info->chip_reg_data.vsense[cnt] =3D get_unaligned_be16(offset_reg_data_p= ); + + if (info->chip_reg_data.vsense_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + info->chip_reg_data.vsense[cnt] =3D + sign_extend32(info->chip_reg_data.vsense[cnt], 15); + + offset_reg_data_p +=3D PAC1944_VBUS_SENSE_REG_LEN; + } + + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + info->chip_reg_data.vbus_avg[cnt] =3D get_unaligned_be16(offset_reg_data= _p); + + if (info->chip_reg_data.vbus_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + info->chip_reg_data.vbus_avg[cnt] =3D + sign_extend32(info->chip_reg_data.vbus_avg[cnt], 15); + + offset_reg_data_p +=3D PAC1944_VBUS_SENSE_REG_LEN; + } + + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + info->chip_reg_data.vsense_avg[cnt] =3D get_unaligned_be16(offset_reg_da= ta_p); + + if (info->chip_reg_data.vsense_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + info->chip_reg_data.vsense_avg[cnt] =3D + sign_extend32(info->chip_reg_data.vsense_avg[cnt], 15); + + offset_reg_data_p +=3D PAC1944_VBUS_SENSE_REG_LEN; + } + + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + info->chip_reg_data.vpower[cnt] =3D get_unaligned_be32(offset_reg_data_p= ) >> 2; + + if (info->chip_reg_data.vbus_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG || + info->chip_reg_data.vsense_mode[cnt] !=3D PAC1944_UNIPOLAR_FSR_CFG) + info->chip_reg_data.vpower[cnt] =3D + sign_extend32(info->chip_reg_data.vpower[cnt], 29); + + offset_reg_data_p +=3D PAC1944_VPOWER_REG_LEN; + } + + return 0; +} + +static int pac1944_retrieve_data(struct pac1944_chip_info *info, u32 wait_= time) +{ + int ret; + + /* + * Check if the minimal elapsed time has passed and if so, + * re-read the chip, otherwise the cached info is just fine. + */ + if (!time_after(jiffies, info->chip_reg_data.jiffies_tstamp + + msecs_to_jiffies(PAC1944_MIN_POLLING_TIME_MS))) + return 0; + + /* We need to re-read the chip values */ + ret =3D pac1944_reg_snapshot(info, true, PAC1944_REFRESH_REG_ADDR, wait_t= ime); + + /* + * Re-schedule the work for the read registers timeout + * (to prevent chip regs saturation) + */ + mod_delayed_work(system_percpu_wq, &info->work_chip_rfsh, + msecs_to_jiffies(PAC1944_MAX_RFSH_LIMIT_MS)); + + return ret; +} + +static ssize_t pac1944_in_power_acc_raw_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + s64 curr_energy, int_part; + int ret; + int rem; + + ret =3D pac1944_retrieve_data(info, PAC1944_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + /* + * Expresses the 64 bit energy value as a + * 64 bit integer and a 32 bit nano value + */ + curr_energy =3D info->chip_reg_data.acc_val[this_attr->address]; + int_part =3D div_s64_rem(curr_energy, 1000000000, &rem); + + if (rem < 0) + return sysfs_emit(buf, "-%lld.%09u\n", abs(int_part), -rem); + else + return sysfs_emit(buf, "%lld.%09u\n", int_part, abs(rem)); +} + +static ssize_t pac1944_in_power_acc_scale_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + unsigned int shunt, rem; + u64 tmp, ref; + + if (info->is_pac195x_family) + ref =3D PAC195X_MAX_VPOWER_RSHIFTED_BY_29B; + else + ref =3D PAC194X_MAX_VPOWER_RSHIFTED_BY_29B; + + if ((info->chip_reg_data.vbus_mode[this_attr->address] =3D=3D PAC1944_UNI= POLAR_FSR_CFG && + info->chip_reg_data.vsense_mode[this_attr->address] =3D=3D PAC1944_U= NIPOLAR_FSR_CFG) || + info->chip_reg_data.vbus_mode[this_attr->address] =3D=3D PAC1944_BIPO= LAR_HALF_FSR_CFG || + info->chip_reg_data.vsense_mode[this_attr->address] =3D=3D PAC1944_BI= POLAR_HALF_FSR_CFG) + ref =3D ref >> 1; + + shunt =3D info->shunts[this_attr->address]; + + tmp =3D div_u64(ref * 1000000000LL, shunt); + rem =3D do_div(tmp, 1000000000LL); + + return sysfs_emit(buf, "%lld.%09u\n", tmp, rem); +} + +static ssize_t pac1944_in_enable_acc_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", info->enable_acc[this_attr->address]); +} + +static ssize_t pac1944_in_enable_acc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + bool val; + + if (kstrtobool(buf, &val)) { + dev_err(dev, "Value is not valid\n"); + return -EINVAL; + } + + scoped_guard(mutex, &info->lock) { + info->enable_acc[this_attr->address] =3D val ? true : false; + if (val) + info->chip_reg_data.acc_val[this_attr->address] =3D 0; + } + + return count; +} + +static ssize_t pac1944_in_current_acc_raw_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + int ret; + + ret =3D pac1944_retrieve_data(info, PAC1944_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%lld\n", info->chip_reg_data.acc_val[this_attr->a= ddress]); +} + +static ssize_t pac1944_in_current_acc_scale_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr =3D to_iio_dev_attr(attr); + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + u64 tmp_u64, ref; + int shunt, rem; + + /* + * Currents - scale for mA - depends on the channel's shunt value + * (100mV * 1000000) / (2^16 * shunt(uOhm)) + */ + ref =3D PAC1944_MAX_VSENSE_NANO; + + switch (info->chip_reg_data.vsense_mode[this_attr->address]) { + case PAC1944_UNIPOLAR_FSR_CFG: + case PAC1944_BIPOLAR_HALF_FSR_CFG: + shunt =3D info->shunts[this_attr->address]; + break; + case PAC1944_BIPOLAR_FSR_CFG: + ref =3D ref << 1; + shunt =3D info->shunts[this_attr->address]; + break; + default: + return -EINVAL; + } + + /* + * Increasing precision + * (100mV * 1000000 * 1000000000) / 2^16 ) + */ + tmp_u64 =3D div_u64(ref, shunt); + rem =3D do_div(tmp_u64, 1000000000LL); + + return sysfs_emit(buf, "%lld.%09u\n", tmp_u64, rem); +} + +static IIO_DEVICE_ATTR(in_energy1_raw, 0444, pac1944_in_power_acc_raw_show= , NULL, 0); +static IIO_DEVICE_ATTR(in_energy2_raw, 0444, pac1944_in_power_acc_raw_show= , NULL, 1); +static IIO_DEVICE_ATTR(in_energy3_raw, 0444, pac1944_in_power_acc_raw_show= , NULL, 2); +static IIO_DEVICE_ATTR(in_energy4_raw, 0444, pac1944_in_power_acc_raw_show= , NULL, 3); + +static IIO_DEVICE_ATTR(in_energy1_scale, 0444, pac1944_in_power_acc_scale_= show, NULL, 0); +static IIO_DEVICE_ATTR(in_energy2_scale, 0444, pac1944_in_power_acc_scale_= show, NULL, 1); +static IIO_DEVICE_ATTR(in_energy3_scale, 0444, pac1944_in_power_acc_scale_= show, NULL, 2); +static IIO_DEVICE_ATTR(in_energy4_scale, 0444, pac1944_in_power_acc_scale_= show, NULL, 3); + +static IIO_DEVICE_ATTR(in_energy1_en, 0644, pac1944_in_enable_acc_show, + pac1944_in_enable_acc_store, 0); +static IIO_DEVICE_ATTR(in_energy2_en, 0644, pac1944_in_enable_acc_show, + pac1944_in_enable_acc_store, 1); +static IIO_DEVICE_ATTR(in_energy3_en, 0644, pac1944_in_enable_acc_show, + pac1944_in_enable_acc_store, 2); +static IIO_DEVICE_ATTR(in_energy4_en, 0644, pac1944_in_enable_acc_show, + pac1944_in_enable_acc_store, 3); + +static IIO_DEVICE_ATTR(in_current_acc1_raw, 0444, pac1944_in_current_acc_r= aw_show, NULL, 0); +static IIO_DEVICE_ATTR(in_current_acc2_raw, 0444, pac1944_in_current_acc_r= aw_show, NULL, 1); +static IIO_DEVICE_ATTR(in_current_acc3_raw, 0444, pac1944_in_current_acc_r= aw_show, NULL, 2); +static IIO_DEVICE_ATTR(in_current_acc4_raw, 0444, pac1944_in_current_acc_r= aw_show, NULL, 3); + +static IIO_DEVICE_ATTR(in_current_acc1_scale, 0444, pac1944_in_current_acc= _scale_show, NULL, 0); +static IIO_DEVICE_ATTR(in_current_acc2_scale, 0444, pac1944_in_current_acc= _scale_show, NULL, 1); +static IIO_DEVICE_ATTR(in_current_acc3_scale, 0444, pac1944_in_current_acc= _scale_show, NULL, 2); +static IIO_DEVICE_ATTR(in_current_acc4_scale, 0444, pac1944_in_current_acc= _scale_show, NULL, 3); + +static IIO_DEVICE_ATTR(in_current_acc1_en, 0644, pac1944_in_enable_acc_sho= w, + pac1944_in_enable_acc_store, 0); +static IIO_DEVICE_ATTR(in_current_acc2_en, 0644, pac1944_in_enable_acc_sho= w, + pac1944_in_enable_acc_store, 1); +static IIO_DEVICE_ATTR(in_current_acc3_en, 0644, pac1944_in_enable_acc_sho= w, + pac1944_in_enable_acc_store, 2); +static IIO_DEVICE_ATTR(in_current_acc4_en, 0644, pac1944_in_enable_acc_sho= w, + pac1944_in_enable_acc_store, 3); + +static struct attribute *pac1944_power_acc_attr[] =3D { + &iio_dev_attr_in_energy1_raw.dev_attr.attr, + &iio_dev_attr_in_energy2_raw.dev_attr.attr, + &iio_dev_attr_in_energy3_raw.dev_attr.attr, + &iio_dev_attr_in_energy4_raw.dev_attr.attr, + &iio_dev_attr_in_energy1_scale.dev_attr.attr, + &iio_dev_attr_in_energy2_scale.dev_attr.attr, + &iio_dev_attr_in_energy3_scale.dev_attr.attr, + &iio_dev_attr_in_energy4_scale.dev_attr.attr, + &iio_dev_attr_in_energy1_en.dev_attr.attr, + &iio_dev_attr_in_energy2_en.dev_attr.attr, + &iio_dev_attr_in_energy3_en.dev_attr.attr, + &iio_dev_attr_in_energy4_en.dev_attr.attr, + NULL +}; + +static struct attribute *pac1944_current_acc_attr[] =3D { + &iio_dev_attr_in_current_acc1_raw.dev_attr.attr, + &iio_dev_attr_in_current_acc2_raw.dev_attr.attr, + &iio_dev_attr_in_current_acc3_raw.dev_attr.attr, + &iio_dev_attr_in_current_acc4_raw.dev_attr.attr, + &iio_dev_attr_in_current_acc1_scale.dev_attr.attr, + &iio_dev_attr_in_current_acc2_scale.dev_attr.attr, + &iio_dev_attr_in_current_acc3_scale.dev_attr.attr, + &iio_dev_attr_in_current_acc4_scale.dev_attr.attr, + &iio_dev_attr_in_current_acc1_en.dev_attr.attr, + &iio_dev_attr_in_current_acc2_en.dev_attr.attr, + &iio_dev_attr_in_current_acc3_en.dev_attr.attr, + &iio_dev_attr_in_current_acc4_en.dev_attr.attr, + NULL +}; + +static int pac1944_prep_custom_attributes(struct pac1944_chip_info *info, + struct iio_dev *indio_dev) +{ + struct attribute **pac1944_custom_attrs, **tmp_attr; + struct i2c_client *client =3D info->client; + struct attribute_group *pac1944_group; + int active_channels_count =3D 0; + int custom_attr_cnt; + int ch, i, j; + + active_channels_count =3D info->num_enabled_channels; + + pac1944_group =3D devm_kzalloc(&client->dev, sizeof(*pac1944_group), GFP_= KERNEL); + if (!pac1944_group) + return -ENOMEM; + + /* + * Attributes for channel X: + * - in_shunt_value_X + * - one of attributes: + * - in_power_accX_raw,in_power_accX_scale and in_power_accX_en + * - in_current_accX_raw, in_current_accX_scale and in_current_accX_en + */ + custom_attr_cnt =3D PAC1944_COMMON_DEVATTR * active_channels_count; + custom_attr_cnt +=3D PAC1944_ACC_DEVATTR * active_channels_count; + + pac1944_custom_attrs =3D devm_kcalloc(&client->dev, custom_attr_cnt + 1, + sizeof(*pac1944_custom_attrs), GFP_KERNEL); + if (!pac1944_custom_attrs) + return -ENOMEM; + + j =3D 0; + + for_each_set_bit(ch, &info->active_channels_mask, info->phys_channels) { + for (i =3D 0; i < PAC1944_COMMON_DEVATTR; i++) + pac1944_custom_attrs[j++] =3D + pac1944_all_attrs[PAC1944_COMMON_DEVATTR * ch + i]; + + switch (info->chip_reg_data.accumulation_mode[ch]) { + case PAC1944_ACCMODE_VPOWER: + tmp_attr =3D pac1944_power_acc_attr; + break; + case PAC1944_ACCMODE_VSENSE: + tmp_attr =3D pac1944_current_acc_attr; + break; + default: + return -EINVAL; + } + + pac1944_custom_attrs[j++] =3D tmp_attr[ch]; + pac1944_custom_attrs[j++] =3D tmp_attr[PAC1944_MAX_CH + ch]; + pac1944_custom_attrs[j++] =3D tmp_attr[2 * PAC1944_MAX_CH + ch]; + } + + pac1944_group->attrs =3D pac1944_custom_attrs; + info->iio_info.attrs =3D pac1944_group; + + return 0; +} + +static int pac1944_frequency_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + struct i2c_client *client =3D info->client; + u16 tmp_u16; + int ret; + + ret =3D i2c_smbus_read_word_swapped(client, PAC1944_CTRL_ACT_REG_ADDR); + if (ret < 0) { + dev_err(&client->dev, "cannot read PAC1944 regs from 0x%02X\n", + PAC1944_CTRL_ACT_REG_ADDR); + return ret; + } + + tmp_u16 =3D ret; + tmp_u16 &=3D ~PAC1944_CTRL_SAMPLE_MASK; + tmp_u16 |=3D FIELD_PREP(PAC1944_CTRL_SAMPLE_MASK, mode); + + scoped_guard(mutex, &info->lock) { + ret =3D i2c_smbus_write_word_swapped(client, PAC1944_CTRL_REG_ADDR, tmp_= u16); + if (ret < 0) { + dev_err(&client->dev, "Failed to configure sampling mode\n"); + return ret; + } + + info->sampling_mode =3D mode; + info->chip_reg_data.ctrl_act_reg =3D tmp_u16; + } + + return pac1944_retrieve_data(info, PAC1944_MIN_UPDATE_WAIT_TIME_US); +} + +static int pac1944_frequency_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + + return info->sampling_mode; +} + +static const struct iio_enum sampling_mode_enum =3D { + .items =3D pac1944_frequency_avail, + .num_items =3D ARRAY_SIZE(pac1944_frequency_avail), + .set =3D pac1944_frequency_set, + .get =3D pac1944_frequency_get, +}; + +static const struct iio_chan_spec_ext_info pac1944_ext_info[] =3D { + IIO_ENUM("sampling_frequency", IIO_SHARED_BY_ALL, &sampling_mode_enum), + { + .name =3D "sampling_frequency_available", + .shared =3D IIO_SHARED_BY_ALL, + .read =3D iio_enum_available_read, + .private =3D (uintptr_t)&sampling_mode_enum, + }, + { } +}; + +static int pac1944_read_raw(struct iio_dev *indio_dev, struct iio_chan_spe= c const *chan, + int *val, int *val2, long mask) +{ + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + int ret, idx; + u64 tmp; + + ret =3D pac1944_retrieve_data(info, PAC1944_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + /* + * In the datasheet channels are referred to as 1 to 4. For AVG the index + * should be between 5 to 8. To calculate PAC1944_CH_VOLTAGE_AVERAGE and + * PAC1944_CH_CURRENT_AVERAGE real index, we need to remove the added + * offset (PAC1944_MAX_CH). + */ + idx =3D (chan->channel - 1) % PAC1944_MAX_CH; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + *val =3D info->chip_reg_data.vbus[idx]; + return IIO_VAL_INT; + case IIO_CURRENT: + *val =3D info->chip_reg_data.vsense[idx]; + return IIO_VAL_INT; + case IIO_POWER: + *val =3D info->chip_reg_data.vpower[idx]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_AVERAGE_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + *val =3D info->chip_reg_data.vbus_avg[idx]; + return IIO_VAL_INT; + case IIO_CURRENT: + *val =3D info->chip_reg_data.vsense_avg[idx]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case PAC1944_VBUS_1_ADDR: + case PAC1944_VBUS_2_ADDR: + case PAC1944_VBUS_3_ADDR: + case PAC1944_VBUS_4_ADDR: + case PAC1944_VBUS_AVG_1_ADDR: + case PAC1944_VBUS_AVG_2_ADDR: + case PAC1944_VBUS_AVG_3_ADDR: + case PAC1944_VBUS_AVG_4_ADDR: + if (info->is_pac195x_family) + *val =3D PAC195X_VOLTAGE_MILLIVOLTS_MAX; + else + *val =3D PAC194X_VOLTAGE_MILLIVOLTS_MAX; + + switch (info->chip_reg_data.vbus_mode[idx]) { + case PAC1944_UNIPOLAR_FSR_CFG: + case PAC1944_BIPOLAR_HALF_FSR_CFG: + *val2 =3D PAC1944_VOLTAGE_16B_RES; + break; + case PAC1944_BIPOLAR_FSR_CFG: + *val2 =3D PAC1944_VOLTAGE_15B_RES; + break; + default: + return -EINVAL; + } + return IIO_VAL_FRACTIONAL_LOG2; + /* + * Currents - scale for mA - depends on the + * channel's shunt value + * (100mV * 1000000) / (2^16 * shunt(microOhm)) + */ + case PAC1944_VSENSE_1_ADDR: + case PAC1944_VSENSE_2_ADDR: + case PAC1944_VSENSE_3_ADDR: + case PAC1944_VSENSE_4_ADDR: + case PAC1944_VSENSE_AVG_1_ADDR: + case PAC1944_VSENSE_AVG_2_ADDR: + case PAC1944_VSENSE_AVG_3_ADDR: + case PAC1944_VSENSE_AVG_4_ADDR: + *val =3D PAC1944_MAX_VSENSE_RSHIFTED_BY_15B; + switch (info->chip_reg_data.vsense_mode[idx]) { + case PAC1944_UNIPOLAR_FSR_CFG: + case PAC1944_BIPOLAR_HALF_FSR_CFG: + *val =3D *val >> 1; + *val2 =3D info->shunts[idx]; + break; + case PAC1944_BIPOLAR_FSR_CFG: + *val2 =3D info->shunts[idx]; + break; + default: + return -EINVAL; + } + return IIO_VAL_FRACTIONAL; + /* + * Power - mW - it will use the combined scale + * for current and voltage + * current(mA) * voltage(mV) =3D power (uW) + */ + case PAC1944_VPOWER_1_ADDR: + case PAC1944_VPOWER_2_ADDR: + case PAC1944_VPOWER_3_ADDR: + case PAC1944_VPOWER_4_ADDR: + if (info->is_pac195x_family) + tmp =3D PAC195X_PRODUCT_VOLTAGE_PV_FSR; + else + tmp =3D PAC194X_PRODUCT_VOLTAGE_PV_FSR; + + do_div(tmp, info->shunts[idx]); + *val =3D (int)tmp; + if ((info->chip_reg_data.vbus_mode[idx] =3D=3D PAC1944_UNIPOLAR_FSR_CFG= && + info->chip_reg_data.vsense_mode[idx] =3D=3D PAC1944_UNIPOLAR_FSR_C= FG) || + info->chip_reg_data.vbus_mode[idx] =3D=3D PAC1944_BIPOLAR_HALF_FSR_= CFG || + info->chip_reg_data.vsense_mode[idx] =3D=3D PAC1944_BIPOLAR_HALF_FS= R_CFG) + *val2 =3D PAC1944_POWER_30B_RES; + else + *val2 =3D PAC1944_POWER_29B_RES; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int pac1944_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct pac1944_chip_info *info =3D iio_priv(indio_dev); + int idx; + + /* + * In the datasheet channels are referred to as 1 to 4. For AVG the index + * should be between 5 to 8. To calculate PAC1944_CH_VOLTAGE_AVERAGE and + * PAC1944_CH_CURRENT_AVERAGE real index, we need to remove the added + * offset (PAC1944_MAX_CH). + */ + idx =3D (chan->channel - 1) % PAC1944_MAX_CH; + + switch (chan->address) { + case PAC1944_VBUS_1_ADDR: + case PAC1944_VBUS_2_ADDR: + case PAC1944_VBUS_3_ADDR: + case PAC1944_VBUS_4_ADDR: + if (info->labels[idx]) + return sysfs_emit(label, "%s_VBUS_%d\n", info->labels[idx], idx + 1); + + return sysfs_emit(label, "VBUS_%d\n", idx + 1); + case PAC1944_VBUS_AVG_1_ADDR: + case PAC1944_VBUS_AVG_2_ADDR: + case PAC1944_VBUS_AVG_3_ADDR: + case PAC1944_VBUS_AVG_4_ADDR: + if (info->labels[idx]) + return sysfs_emit(label, "%s_VBUS_AVG_%d\n", info->labels[idx], idx + 1= ); + + return sysfs_emit(label, "VBUS_AVG_%d\n", idx + 1); + case PAC1944_VSENSE_1_ADDR: + case PAC1944_VSENSE_2_ADDR: + case PAC1944_VSENSE_3_ADDR: + case PAC1944_VSENSE_4_ADDR: + if (info->labels[idx]) + return sysfs_emit(label, "%s_IBUS_%d\n", info->labels[idx], idx + 1); + + return sysfs_emit(label, "IBUS_%d\n", idx + 1); + case PAC1944_VSENSE_AVG_1_ADDR: + case PAC1944_VSENSE_AVG_2_ADDR: + case PAC1944_VSENSE_AVG_3_ADDR: + case PAC1944_VSENSE_AVG_4_ADDR: + if (info->labels[idx]) + return sysfs_emit(label, "%s_IBUS_AVG_%d\n", info->labels[idx], idx + 1= ); + + return sysfs_emit(label, "IBUS_AVG_%d\n", idx + 1); + case PAC1944_VPOWER_1_ADDR: + case PAC1944_VPOWER_2_ADDR: + case PAC1944_VPOWER_3_ADDR: + case PAC1944_VPOWER_4_ADDR: + if (info->labels[idx]) + return sysfs_emit(label, "%s_POWER_%d\n", info->labels[idx], idx + 1); + + return sysfs_emit(label, "POWER_%d\n", idx + 1); + } + + return 0; +} + +static void pac1944_work_periodic_rfsh(struct work_struct *work) +{ + struct pac1944_chip_info *info =3D to_pac1944_chip_info(to_delayed_work(w= ork)); + struct i2c_client *client =3D info->client; + + dev_dbg(&client->dev, "Periodic refresh\n"); + + pac1944_reg_snapshot(info, true, PAC1944_REFRESH_REG_ADDR, + PAC1944_MIN_UPDATE_WAIT_TIME_US); + + mod_delayed_work(system_percpu_wq, &info->work_chip_rfsh, + msecs_to_jiffies(PAC1944_MAX_RFSH_LIMIT_MS)); +} + +/* + * Universal Unique Identifier (UUID), + * 721F1534-5D27-4B60-9DF4-41A3C4B7DA3A, + * is reserved to Microchip for the PAC194x and PAC195x. + */ +#define PAC1944_DSM_UUID "721F1534-5D27-4B60-9DF4-41A3C4B7DA3A" + +/* + * documentation related to the ACPI device definition + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/Product= Documents/UserGuides/PAC194X_5X-UEFI-BIOS-Integration-and-Microsoft-Windows= -10-and-Windows-11-Energy-Meter-Interface-Device-Driver-Users-Guide-DS50003= 155.pdf + */ +static int pac1944_acpi_parse_channel_config(struct i2c_client *client, + struct pac1944_chip_info *info) +{ + struct device *dev =3D &client->dev; + const struct acpi_device_id *id; + unsigned short bi_dir_mask; + union acpi_object *rez; + acpi_handle handle; + guid_t guid; + int i; + + handle =3D ACPI_HANDLE(dev); + + id =3D acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + + guid_parse(PAC1944_DSM_UUID, &guid); + + rez =3D acpi_evaluate_dsm(handle, &guid, 0, PAC1944_ACPI_GET_NAMES, NULL); + if (!rez) + return -EINVAL; + + if (rez->package.count > PAC1944_MAX_CH) { + ACPI_FREE(rez); + return -EINVAL; + } + + for (i =3D 0; i < rez->package.count; i++) { + if (rez->package.elements[i].type !=3D ACPI_TYPE_STRING) { + ACPI_FREE(rez); + return -EINVAL; + } + + info->labels[i] =3D devm_kmemdup(dev, rez->package.elements[i].string.po= inter, + (size_t)rez->package.elements[i].string.length + 1, + GFP_KERNEL); + if (!info->labels[i]) { + ACPI_FREE(rez); + return -ENOMEM; + } + + info->labels[i][rez->package.elements[i].string.length] =3D '\0'; + } + + ACPI_FREE(rez); + + rez =3D acpi_evaluate_dsm(handle, &guid, 1, PAC1944_ACPI_GET_UOHMS_VALS, = NULL); + if (!rez) + return -EINVAL; + + if (rez->package.count > PAC1944_MAX_CH) { + ACPI_FREE(rez); + return -EINVAL; + } + + for (i =3D 0; i < rez->package.count; i++) { + if (rez->package.elements[i].type !=3D ACPI_TYPE_INTEGER) { + ACPI_FREE(rez); + return -EINVAL; + } + + info->shunts[i] =3D rez->package.elements[i].integer.value; + if (info->shunts[i]) + set_bit(i, &info->active_channels_mask); + } + + ACPI_FREE(rez); + + rez =3D acpi_evaluate_dsm(handle, &guid, 1, PAC1944_ACPI_GET_BIPOLAR_SETT= INGS, NULL); + if (!rez) + return -EINVAL; + + if (rez->package.count !=3D 2 * PAC1944_MAX_CH) { + ACPI_FREE(rez); + return -EINVAL; + } + + for_each_set_bit(i, &info->active_channels_mask, info->phys_channels) { + if (rez->package.elements[i].type !=3D ACPI_TYPE_INTEGER) { + ACPI_FREE(rez); + return -EINVAL; + } + + bi_dir_mask =3D rez->package.elements[i].integer.value; + + if (bi_dir_mask =3D=3D PAC1944_UNIPOLAR_FSR_CFG || + bi_dir_mask =3D=3D PAC1944_BIPOLAR_FSR_CFG || + bi_dir_mask =3D=3D PAC1944_BIPOLAR_HALF_FSR_CFG) { + dev_dbg(dev, "VBUS{%d} mode set to: %d\n", i, bi_dir_mask); + info->chip_reg_data.vbus_mode[i] =3D bi_dir_mask; + } else { + return dev_err_probe(dev, -EINVAL, "invalid vbus-mode value on %i\n", i= ); + } + + if (rez->package.elements[i + PAC1944_MAX_CH].type !=3D ACPI_TYPE_INTEGE= R) { + ACPI_FREE(rez); + return -EINVAL; + } + + bi_dir_mask =3D rez->package.elements[i + PAC1944_MAX_CH].integer.value; + + if (bi_dir_mask =3D=3D PAC1944_UNIPOLAR_FSR_CFG || + bi_dir_mask =3D=3D PAC1944_BIPOLAR_FSR_CFG || + bi_dir_mask =3D=3D PAC1944_BIPOLAR_HALF_FSR_CFG) { + dev_dbg(dev, "VSENSE{%d} mode set to: %d\n", i, bi_dir_mask); + info->chip_reg_data.vsense_mode[i] =3D bi_dir_mask; + } else { + return dev_err_probe(dev, -EINVAL, "invalid vsense-mode value on %i\n",= i); + } + } + + ACPI_FREE(rez); + + return 0; +} + +static int pac1944_check_range(s32 *vals, const int ranges[][2], int num_r= anges) +{ + int i; + + for (i =3D 0; i < num_ranges; i++) { + if (vals[0] =3D=3D ranges[i][0] && vals[1] =3D=3D ranges[i][1]) + return i; + } + + return -EINVAL; +} + +static int pac1944_setup_adc_vbus_range(const struct fwnode_handle *child, + struct pac1944_chip_info *info, int idx) +{ + const char *prop_name =3D "microchip,vbus-input-range-microvolt"; + struct i2c_client *client =3D info->client; + struct device *dev =3D &client->dev; + bool is_bipolar, is_half_fsr; + unsigned int tbl_len; + s32 vals[2]; + int ret; + + /* default value is unipolar and Full Scale Range */ + is_bipolar =3D false; + is_half_fsr =3D false; + + ret =3D fwnode_property_read_u32_array(child, prop_name, vals, 2); + if (!ret) { + if (info->is_pac195x_family) { + tbl_len =3D ARRAY_SIZE(pac1954_adc_range_tbl); + ret =3D pac1944_check_range(vals, pac1954_adc_range_tbl, tbl_len); + } else { + tbl_len =3D ARRAY_SIZE(pac1944_adc_range_tbl); + ret =3D pac1944_check_range(vals, pac1944_adc_range_tbl, tbl_len); + } + if (ret < 0) { + return dev_err_probe(dev, -EINVAL, + "Invalid value %d, %d for prop %s\n", + vals[0], vals[1], prop_name); + } + + if (ret !=3D PAC1944_ADC_FULL_RANGE_UNIPOLAR) + is_bipolar =3D true; + if (ret =3D=3D PAC1944_ADC_HALF_RANGE_BIPOLAR) + is_half_fsr =3D true; + } + + info->chip_reg_data.vbus_mode[idx] =3D PAC1944_UNIPOLAR_FSR_CFG; + if (is_half_fsr) + info->chip_reg_data.vbus_mode[idx] =3D PAC1944_BIPOLAR_HALF_FSR_CFG; + else if (is_bipolar) + info->chip_reg_data.vbus_mode[idx] =3D PAC1944_BIPOLAR_FSR_CFG; + + return 0; +} + +static int pac1944_setup_adc_vsense_range(const struct fwnode_handle *chil= d, + struct pac1944_chip_info *info, int idx) +{ + const char *prop_name =3D "microchip,vsense-input-range-microvolt"; + struct i2c_client *client =3D info->client; + struct device *dev =3D &client->dev; + bool is_bipolar, is_half_fsr; + unsigned int tbl_len; + s32 vals[2]; + int ret; + + /* default value is unipolar and Full Scale Range */ + is_bipolar =3D false; + is_half_fsr =3D false; + + ret =3D fwnode_property_read_u32_array(child, prop_name, vals, 2); + if (!ret) { + tbl_len =3D ARRAY_SIZE(pac1944_adc_vsense_range_tbl); + ret =3D pac1944_check_range(vals, pac1944_adc_vsense_range_tbl, tbl_len); + if (ret < 0) { + return dev_err_probe(dev, -EINVAL, + "Invalid value %d, %d for prop %s\n", + vals[0], vals[1], prop_name); + } + + if (ret !=3D PAC1944_ADC_FULL_RANGE_UNIPOLAR) + is_bipolar =3D true; + if (ret =3D=3D PAC1944_ADC_HALF_RANGE_BIPOLAR) + is_half_fsr =3D true; + } + + info->chip_reg_data.vsense_mode[idx] =3D PAC1944_UNIPOLAR_FSR_CFG; + if (is_half_fsr) + info->chip_reg_data.vsense_mode[idx] =3D PAC1944_BIPOLAR_HALF_FSR_CFG; + else if (is_bipolar) + info->chip_reg_data.vsense_mode[idx] =3D PAC1944_BIPOLAR_FSR_CFG; + + return 0; +} + +static int pac1944_of_parse_channel_config(struct i2c_client *client, + struct pac1944_chip_info *info) +{ + struct device *dev =3D &client->dev; + unsigned int current_channel; + int ret, acc_mode; + u32 idx; + + current_channel =3D 1; + + device_for_each_child_node_scoped(dev, child) { + ret =3D fwnode_property_read_u32(child, "reg", &idx); + if (ret) + return dev_err_probe(dev, ret, "reading invalid channel index\n"); + + if (current_channel > info->phys_channels || idx > info->phys_channels |= | idx =3D=3D 0) + return dev_err_probe(dev, -EINVAL, "invalid channel index %d value\n", = idx); + + /* adjust idx to match channel index (1 to 4) from the datasheet */ + idx--; + + if (test_and_set_bit(idx, &info->active_channels_mask)) + return dev_err_probe(dev, -EINVAL, "duplicate reg value %u\n", idx + 1); + + ret =3D fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", + &info->shunts[idx]); + if (ret) + return dev_err_probe(dev, ret, "%s: invalid shunt-resistor value\n", + fwnode_get_name(child)); + + fwnode_property_read_string(child, "label", (const char **)&info->labels= [idx]); + + ret =3D pac1944_setup_adc_vbus_range(child, info, idx); + if (ret) + return ret; + + ret =3D pac1944_setup_adc_vsense_range(child, info, idx); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(child, "microchip,accumulation-mode", &= acc_mode); + if (ret) + return dev_err_probe(dev, ret, "invalid accumulation-mode value on %s\n= ", + fwnode_get_name(child)); + if (acc_mode =3D=3D PAC1944_ACCMODE_VPOWER || + acc_mode =3D=3D PAC1944_ACCMODE_VSENSE) { + dev_dbg(dev, "Accumulation{%d} mode set to: %d\n", idx, acc_mode); + info->chip_reg_data.accumulation_mode[idx] =3D acc_mode; + } else { + return dev_err_probe(dev, -EINVAL, + "invalid mode for accumulator value on %s\n", + fwnode_get_name(child)); + } + current_channel++; + } + + return 0; +} + +static void pac1944_cancel_delayed_work(void *dwork) +{ + cancel_delayed_work_sync(dwork); +} + +static int pac1944_chip_identify(struct iio_dev *indio_dev, struct pac1944= _chip_info *info) +{ + struct i2c_client *client =3D info->client; + u8 chip_rev[3]; + int ret; + + ret =3D i2c_smbus_read_i2c_block_data(client, PAC1944_PID_REG_ADDR, + sizeof(chip_rev), + chip_rev); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "cannot read revision\n"); + + dev_info(&client->dev, "Chip revision: 0x%02X\n", chip_rev[2]); + + if (chip_rev[0] >=3D PAC_PRODUCT_ID_1941 && chip_rev[0] <=3D PAC_PRODUCT_= ID_1942_2) { + info->is_pac195x_family =3D false; + } else if (chip_rev[0] >=3D PAC_PRODUCT_ID_1951 && chip_rev[0] <=3D PAC_P= RODUCT_ID_1952_2) { + info->is_pac195x_family =3D true; + } else { + dev_err(&client->dev, + "product ID (0x%02X, 0x%02X, 0x%02X) for this part doesn't match\n", + chip_rev[0], chip_rev[1], chip_rev[2]); + return -EINVAL; + } + + switch (chip_rev[0]) { + case PAC_PRODUCT_ID_1941: + info->phys_channels =3D pac1941_chip_config.phys_channels; + indio_dev->name =3D pac1941_chip_config.name; + break; + case PAC_PRODUCT_ID_1942: + info->phys_channels =3D pac1942_chip_config.phys_channels; + indio_dev->name =3D pac1942_chip_config.name; + break; + case PAC_PRODUCT_ID_1943: + info->phys_channels =3D pac1943_chip_config.phys_channels; + indio_dev->name =3D pac1943_chip_config.name; + break; + case PAC_PRODUCT_ID_1944: + info->phys_channels =3D pac1944_chip_config.phys_channels; + indio_dev->name =3D pac1944_chip_config.name; + break; + case PAC_PRODUCT_ID_1941_2: + info->phys_channels =3D pac1941_2_chip_config.phys_channels; + indio_dev->name =3D pac1941_2_chip_config.name; + break; + case PAC_PRODUCT_ID_1942_2: + info->phys_channels =3D pac1942_2_chip_config.phys_channels; + indio_dev->name =3D pac1942_2_chip_config.name; + break; + case PAC_PRODUCT_ID_1951: + info->phys_channels =3D pac1951_chip_config.phys_channels; + indio_dev->name =3D pac1951_chip_config.name; + break; + case PAC_PRODUCT_ID_1952: + info->phys_channels =3D pac1952_chip_config.phys_channels; + indio_dev->name =3D pac1952_chip_config.name; + break; + case PAC_PRODUCT_ID_1953: + info->phys_channels =3D pac1953_chip_config.phys_channels; + indio_dev->name =3D pac1953_chip_config.name; + break; + case PAC_PRODUCT_ID_1954: + info->phys_channels =3D pac1954_chip_config.phys_channels; + indio_dev->name =3D pac1954_chip_config.name; + break; + case PAC_PRODUCT_ID_1951_2: + info->phys_channels =3D pac1951_2_chip_config.phys_channels; + indio_dev->name =3D pac1951_2_chip_config.name; + break; + case PAC_PRODUCT_ID_1952_2: + info->phys_channels =3D pac1952_2_chip_config.phys_channels; + indio_dev->name =3D pac1952_2_chip_config.name; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pac1944_chip_configure(struct pac1944_chip_info *info) +{ + struct i2c_client *client =3D info->client; + u32 wait_time; + u16 cfg =3D 0; + u8 acc_cfg; + int ret, i; + + /* + * Count how many channels are enabled and store this information within + * the driver data. + */ + info->num_enabled_channels =3D hweight_long(info->active_channels_mask); + + /* get sampling rate from PAC */ + ret =3D i2c_smbus_read_word_swapped(client, PAC1944_CTRL_REG_ADDR); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "cannot read CTRL reg\n"); + + info->sampling_mode =3D FIELD_GET(PAC1944_CTRL_SAMPLE_MASK, ret); + + if (info->sampling_mode > PAC1944_SAMP_8SPS) + return dev_err_probe(&client->dev, -EINVAL, "Unsupported sample mode\n"); + + /* + * The current/voltage can be measured unidirectional, bidirectional or h= alf FSR + * no SLOW triggered REFRESH, clear POR + */ + for_each_set_bit(i, &info->active_channels_mask, PAC1944_MAX_CH) + cfg |=3D (info->chip_reg_data.vbus_mode[i] << (6 - 2 * i)) | + info->chip_reg_data.vsense_mode[i] << (14 - 2 * i); + + ret =3D i2c_smbus_write_word_swapped(client, PAC1944_NEG_PWR_FSR_REG_ADDR= , cfg); + if (ret) + return dev_err_probe(&client->dev, ret, "cannot write NEG_PWR_FSR reg\n"= ); + + ret =3D i2c_smbus_write_word_data(client, PAC1944_SLOW_REG_ADDR, 0); + if (ret) + return dev_err_probe(&client->dev, ret, "cannot write SLOW reg\n"); + + /* Write the CHANNEL_N_OFF from CTRL REGISTER */ + cfg =3D FIELD_PREP(PAC1944_CTRL_SAMPLE_MASK, info->sampling_mode) | + FIELD_PREP(PAC1944_CTRL_GPIO_ALERT2_MASK, 0) | + FIELD_PREP(PAC1944_CTRL_SLOW_ALERT1_MASK, 0) | + FIELD_PREP(PAC1944_CTRL_CH_1_OFF_MASK, !test_bit(0, &info->active_channe= ls_mask)) | + FIELD_PREP(PAC1944_CTRL_CH_2_OFF_MASK, !test_bit(1, &info->active_channe= ls_mask)) | + FIELD_PREP(PAC1944_CTRL_CH_3_OFF_MASK, !test_bit(2, &info->active_channe= ls_mask)) | + FIELD_PREP(PAC1944_CTRL_CH_4_OFF_MASK, !test_bit(3, &info->active_channe= ls_mask)); + + ret =3D i2c_smbus_write_word_swapped(client, PAC1944_CTRL_REG_ADDR, cfg); + if (ret) + return dev_err_probe(&client->dev, ret, "cannot write CTRL reg\n"); + + acc_cfg =3D ACCUM_REG(info->chip_reg_data.accumulation_mode[0], + info->chip_reg_data.accumulation_mode[1], + info->chip_reg_data.accumulation_mode[2], + info->chip_reg_data.accumulation_mode[3]); + + ret =3D i2c_smbus_write_byte_data(client, PAC1944_ACCUM_CFG_REG_ADDR, acc= _cfg); + if (ret) + return dev_err_probe(&client->dev, ret, "cannot write ACCUM_CFG reg\n"); + + /* + * Sending a REFRESH to the chip, so the new settings take place + * as well as resetting the accumulators + */ + ret =3D i2c_smbus_write_byte(client, PAC1944_REFRESH_REG_ADDR); + if (ret) + return dev_err_probe(&client->dev, ret, "cannot write REFRESH reg\n"); + + /* + * Get the current (in the chip) sampling speed and compute the + * required timeout based on its value the timeout is 1/sampling_speed + * wait the maximum amount of time to be on the safe side - the + * maximum wait time is for 8sps + */ + wait_time =3D (1024 / pac1944_samp_rate_map_tbl[info->sampling_mode]) * 1= 000; + usleep_range(wait_time, wait_time + 100); + + INIT_DELAYED_WORK(&info->work_chip_rfsh, pac1944_work_periodic_rfsh); + /* Setup the latest moment for reading the regs before saturation */ + mod_delayed_work(system_percpu_wq, &info->work_chip_rfsh, + msecs_to_jiffies(PAC1944_MAX_RFSH_LIMIT_MS)); + + return devm_add_action_or_reset(&client->dev, pac1944_cancel_delayed_work, + &info->work_chip_rfsh); +} + +static const struct iio_chan_spec pac1944_single_channel[] =3D { + PAC1944_VPOWER_CHANNEL(0, PAC1944_VPOWER_1_ADDR), + PAC1944_VBUS_CHANNEL(0, PAC1944_VBUS_1_ADDR), + PAC1944_VSENSE_CHANNEL(0, PAC1944_VSENSE_1_ADDR), + PAC1944_VBUS_AVG_CHANNEL(0, PAC1944_VBUS_AVG_1_ADDR), + PAC1944_VSENSE_AVG_CHANNEL(0, PAC1944_VSENSE_AVG_1_ADDR), +}; + +static int pac1944_prep_iio_channels(struct pac1944_chip_info *info, + struct iio_dev *indio_dev) +{ + struct device *dev =3D &info->client->dev; + int channel_size, attribute_count, cnt; + struct iio_chan_spec *ch_sp; + + /* Finding out dynamically how many IIO channels we need */ + attribute_count =3D 0; + channel_size =3D 0; + + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + /* add the size of the properties of one chip physical channel */ + channel_size +=3D sizeof(pac1944_single_channel); + /* count how many enabled channels we have */ + attribute_count +=3D ARRAY_SIZE(pac1944_single_channel); + dev_dbg(dev, ":%s: Channel %d active\n", __func__, cnt + 1); + } + + ch_sp =3D devm_kzalloc(dev, channel_size, GFP_KERNEL); + if (!ch_sp) + return -ENOMEM; + + /* + * Send the updated dynamic channel structure information towards IIO + * prepare the required field for IIO class registration + */ + indio_dev->num_channels =3D attribute_count; + indio_dev->channels =3D ch_sp; + + /* Populate the dynamic channels and make all the adjustments */ + for_each_set_bit(cnt, &info->active_channels_mask, info->phys_channels) { + memcpy(ch_sp, pac1944_single_channel, sizeof(pac1944_single_channel)); + /* + * In the datasheet channels are referred to as 1 to 4, so we will + * adjust the channel to match channel index (1 to 4) from the + * datasheet. + */ + ch_sp[PAC1944_CH_POWER].channel =3D cnt + 1; + ch_sp[PAC1944_CH_POWER].address =3D cnt + PAC1944_VPOWER_1_ADDR; + ch_sp[PAC1944_CH_VOLTAGE].channel =3D cnt + 1; + ch_sp[PAC1944_CH_VOLTAGE].address =3D cnt + PAC1944_VBUS_1_ADDR; + ch_sp[PAC1944_CH_CURRENT].channel =3D cnt + 1; + ch_sp[PAC1944_CH_CURRENT].address =3D cnt + PAC1944_VSENSE_1_ADDR; + /* + * In order to be able to use labels for PAC1944_CH_VOLTAGE and + * PAC1944_CH_VOLTAGE_AVERAGE, respectively PAC1944_CH_CURRENT + * and PAC1944_CH_CURRENT_AVERAGE we need to use different channel numbe= rs. + * We will add +5 (+1 to maximum PAC channels). + */ + ch_sp[PAC1944_CH_VOLTAGE_AVERAGE].channel =3D cnt + PAC1944_MAX_CH + 1; + ch_sp[PAC1944_CH_VOLTAGE_AVERAGE].address =3D cnt + PAC1944_VBUS_AVG_1_A= DDR; + ch_sp[PAC1944_CH_CURRENT_AVERAGE].channel =3D cnt + PAC1944_MAX_CH + 1; + ch_sp[PAC1944_CH_CURRENT_AVERAGE].address =3D cnt + PAC1944_VSENSE_AVG_1= _ADDR; + + /* advance the pointer */ + ch_sp +=3D ARRAY_SIZE(pac1944_single_channel); + } + + return 0; +} + +static const struct iio_info pac1944_info =3D { + .read_raw =3D pac1944_read_raw, + .read_label =3D pac1944_read_label, +}; + +static int pac1944_probe(struct i2c_client *client) +{ + const struct pac1944_features *chip; + struct device *dev =3D &client->dev; + struct pac1944_chip_info *info; + struct iio_dev *indio_dev; + int cnt, ret; + + indio_dev =3D devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info =3D iio_priv(indio_dev); + info->client =3D client; + + ret =3D pac1944_chip_identify(indio_dev, info); + if (ret < 0) { + dev_dbg(dev, "Failed to identify the device based on HW ID\n"); + /* + * If failed to identify the hardware based on internal registers, + * try using fallback compatible in device tree to deal with + * some newer part number. + */ + chip =3D i2c_get_match_data(client); + if (!chip) + return -EINVAL; + + info->phys_channels =3D chip->phys_channels; + indio_dev->name =3D chip->name; + + switch (chip->prod_id) { + case PAC_PRODUCT_ID_1941: + case PAC_PRODUCT_ID_1942: + case PAC_PRODUCT_ID_1943: + case PAC_PRODUCT_ID_1944: + case PAC_PRODUCT_ID_1941_2: + case PAC_PRODUCT_ID_1942_2: + info->is_pac195x_family =3D false; + break; + case PAC_PRODUCT_ID_1951: + case PAC_PRODUCT_ID_1952: + case PAC_PRODUCT_ID_1953: + case PAC_PRODUCT_ID_1954: + case PAC_PRODUCT_ID_1951_2: + case PAC_PRODUCT_ID_1952_2: + info->is_pac195x_family =3D true; + break; + default: + dev_err(&client->dev, + "Unknown product ID (0x%02X)\n", chip->prod_id); + return -EINVAL; + } + } + + for (cnt =3D 0; cnt < info->phys_channels; cnt++) { + /* always start with accumulation channels enabled */ + info->enable_acc[cnt] =3D true; + } + + if (ACPI_HANDLE(dev)) + ret =3D pac1944_acpi_parse_channel_config(client, info); + else + ret =3D pac1944_of_parse_channel_config(client, info); + + if (ret) + return dev_err_probe(dev, ret, "parameter parsing returned an error\n"); + + ret =3D devm_mutex_init(dev, &info->lock); + if (ret < 0) + return ret; + + ret =3D pac1944_chip_configure(info); + if (ret < 0) + return ret; + + ret =3D pac1944_prep_iio_channels(info, indio_dev); + if (ret < 0) + return ret; + + info->iio_info =3D pac1944_info; + indio_dev->info =3D &info->iio_info; + indio_dev->modes =3D INDIO_DIRECT_MODE; + + ret =3D pac1944_prep_custom_attributes(info, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Can't configure custom attributes for de= vice\n"); + + ret =3D pac1944_reg_snapshot(info, true, PAC1944_REFRESH_REG_ADDR, + PAC1944_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + ret =3D devm_iio_device_register(dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Can't register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id pac1944_id[] =3D { + { .name =3D "pac1941", .driver_data =3D (kernel_ulong_t)&pac1941_chip_con= fig }, + { .name =3D "pac1941-2", .driver_data =3D (kernel_ulong_t)&pac1941_2_chip= _config }, + { .name =3D "pac1942", .driver_data =3D (kernel_ulong_t)&pac1942_chip_con= fig }, + { .name =3D "pac1942-2", .driver_data =3D (kernel_ulong_t)&pac1942_2_chip= _config }, + { .name =3D "pac1943", .driver_data =3D (kernel_ulong_t)&pac1943_chip_con= fig }, + { .name =3D "pac1944", .driver_data =3D (kernel_ulong_t)&pac1944_chip_con= fig }, + { .name =3D "pac1951", .driver_data =3D (kernel_ulong_t)&pac1951_chip_con= fig }, + { .name =3D "pac1951-2", .driver_data =3D (kernel_ulong_t)&pac1951_2_chip= _config }, + { .name =3D "pac1952", .driver_data =3D (kernel_ulong_t)&pac1952_chip_con= fig }, + { .name =3D "pac1952-2", .driver_data =3D (kernel_ulong_t)&pac1952_2_chip= _config }, + { .name =3D "pac1953", .driver_data =3D (kernel_ulong_t)&pac1953_chip_con= fig }, + { .name =3D "pac1954", .driver_data =3D (kernel_ulong_t)&pac1954_chip_con= fig }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pac1944_id); + +static const struct of_device_id pac1944_of_match[] =3D { + { + .compatible =3D "microchip,pac1941", + .data =3D &pac1941_chip_config + }, + { + .compatible =3D "microchip,pac1941-2", + .data =3D &pac1941_2_chip_config + }, + { + .compatible =3D "microchip,pac1942", + .data =3D &pac1942_chip_config + }, + { + .compatible =3D "microchip,pac1942-2", + .data =3D &pac1942_2_chip_config + }, + { + .compatible =3D "microchip,pac1943", + .data =3D &pac1943_chip_config + }, + { + .compatible =3D "microchip,pac1944", + .data =3D &pac1944_chip_config + }, + { + .compatible =3D "microchip,pac1951", + .data =3D &pac1951_chip_config + }, + { + .compatible =3D "microchip,pac1951-2", + .data =3D &pac1951_2_chip_config + }, + { + .compatible =3D "microchip,pac1952", + .data =3D &pac1952_chip_config + }, + { + .compatible =3D "microchip,pac1952-2", + .data =3D &pac1952_2_chip_config + }, + { + .compatible =3D "microchip,pac1953", + .data =3D &pac1953_chip_config + }, + { + .compatible =3D "microchip,pac1954", + .data =3D &pac1954_chip_config + }, + { } +}; +MODULE_DEVICE_TABLE(of, pac1944_of_match); + +static const struct acpi_device_id pac1944_acpi_match[] =3D { + { "MCHP1940", .driver_data =3D (kernel_ulong_t)&pac1944_chip_config }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pac1944_acpi_match); + +static struct i2c_driver pac1944_driver =3D { + .driver =3D { + .name =3D "pac1944", + .of_match_table =3D pac1944_of_match, + .acpi_match_table =3D pac1944_acpi_match + }, + .probe =3D pac1944_probe, + .id_table =3D pac1944_id, +}; +module_i2c_driver(pac1944_driver); + +MODULE_AUTHOR("Marius Cristea "); +MODULE_DESCRIPTION("Microchip PAC194X and PAC195X Power Monitor"); +MODULE_LICENSE("GPL"); --=20 2.51.0