From nobody Tue Dec 2 01:06:07 2025 Received: from mail-ua1-f47.google.com (mail-ua1-f47.google.com [209.85.222.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 A5F4F26D4C3 for ; Fri, 21 Nov 2025 17:16:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763745402; cv=none; b=Ia9laaOC07mZJxPa5AKXtT34r+sNdIHjBKKseR/svI+y+ilbTOl96QS0kgjgOU7Sm4YFe4k1THF30WYo8I+mMolcxel1qm5a0nATNkufApv+Me7JVdVcmESPgPrfGgJFU5i8JTe1/+x6Wb+das88UR9J5nYGAj3uEXrGodB6KvM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763745402; c=relaxed/simple; bh=ehm4ws2yMf2rLsIV55CO1QANq8wV/IBX6JJ6WdGosVY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Imjw5yVCAusSE3ymCmcgYEYRNe/q+56T2xq8YxPZvvwdiahUpSCD3FZfdm15dV4HDE+z3nI9efRAOe5Ckpsl71NuYOf+sjTmGSv7kIdz0ZRNKb0fjxdCK189ILkL+qMTZUTkXSWJRK5ahyQquNRi790kvAhwm80LBDqd1UbTrEc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=XBMSB7Wq; arc=none smtp.client-ip=209.85.222.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XBMSB7Wq" Received: by mail-ua1-f47.google.com with SMTP id a1e0cc1a2514c-93725308c15so1331373241.1 for ; Fri, 21 Nov 2025 09:16:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1763745399; x=1764350199; 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=3Z6055fPh7kB7snV4QcgA3fj8wpQZDtBh6CugABtOxU=; b=XBMSB7Wq+dxTwxiW3EYO7SHxsN6/LPj8A9dHyWDhD0WL2mcnoWny/Womz/m+sVMADy 5UAAh0Wr2jlTj8FnUu6ozXJaA04T66MjpHAGBZnewZmA6zCqcY23OWJ9lTR6oAq+1/jm /E6Br709AJTxVaG+fJkJR+BKaAGQA3oIJDTzKe8sV5SjCBSLtjommY28WJ8elJa7zFbg heLysnCTbRyGBn61is+WXFMvkvV4khULdfMwHeQDFAYAbAGecLAufTcJlEDAQ+yFbHf8 Zdl/2qL3H0Yp0C96nFb0QQHOp95XBqI3kFnVpAKEK1q7L2VHZchXG/vIPIq2fQwT+8W8 Utxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763745399; x=1764350199; 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=3Z6055fPh7kB7snV4QcgA3fj8wpQZDtBh6CugABtOxU=; b=hrie20n73gbXjMWGCEV7v2dC0/u6WruaVM6qC1RmYkwE1dYL9KkMoM+QjJqbzCuMuQ MbVpLP66AkVDVxtBlwBERLJZgrc7kqBe5SC/92/SswP6SJ0me5XvPJR4h2LhecVLDwvT vGQC1euuM3kxXZZF5NpmiYvj9a/uELXAvx4UjO6gDv5MoqRCfdfmRK7CBgHOsvYRONR+ ie8sTxKMmz824GFh13xzeVaUMUmv8I4ZAJziYdZ+9EQdKiM07EXNe3mLT/NAT9uFyTX6 ORA0S6+RCjehOdqEux3VKfIsO1860aHmbH3PtRScNVgrY7DcejxwdcuSA1shh1PCUfUO Ffiw== X-Forwarded-Encrypted: i=1; AJvYcCXR17gaTVMTHhjKev7wjThYK8rcGW3nzgRDSDpym8gkFyHUYuvM92QjCFmXp+cAt5sHVbXcAQB3Z80NF5U=@vger.kernel.org X-Gm-Message-State: AOJu0YyccQ0eLdSkFcpA3l8js7o1PMrsBeM/jhwDdcIapYUQcBSz2+Ty SfxkOYqTki3H3fgWhvAOC5SMkpD5VAk2JNhvKe/WibipHSKIuLGgODIU X-Gm-Gg: ASbGncvcnhB4zZSz25J7R0sRNROL8oeo+xmMwjS4djfZxaPdhitRF89NWC9gByauNvz KM2vFKNUgzS6smNduU4m+Jjh9nxjKWyxcXHj7vvwBUO5tsa0OVxpO/KFrFoXsAaia9FXiyEAgDv B+oZ0NfcJrLjzEAWb+R2wEDhq8WsJh1l4bsuuDuMvf5Rl89J7iAH237AIljIZdQvfR80PXwV+DO xEErYKJW7pqq4iq8TnXM/CuM+AzyTaAJZRmvT+k1WLIvtFQIFsFGyiuLa1Rv9prnXjNVA1/bFDe fuJ51cP857a2kRZM+WR0Sm6XCiBQ7YhGlcqAChdlGSEJMtJ2alNDZQnRQ5UeWqiyOP3fAScaHuB Lq8461gVn8/zYYYsJPOan4z6xNqY2spNslejnel+z7NIu8TnS0Mkm0qKqu6bWCDYAntYlmJ9OUl sGCIgspuV7kk1a X-Google-Smtp-Source: AGHT+IEfi8liiww2gOeL0LR2tzYWdsW6iCYd283+pkcr7skpg77hS2K2UbiAfPjBof34fVdlgv5H/g== X-Received: by 2002:a05:6102:424b:b0:5dd:c53b:75cc with SMTP id ada2fe7eead31-5e1dcf41d12mr1343612137.13.1763745399592; Fri, 21 Nov 2025 09:16:39 -0800 (PST) Received: from [192.168.100.70] ([2800:bf0:82:3d2:875c:6c76:e06b:3095]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-93c562728fdsm2554368241.7.2025.11.21.09.16.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 21 Nov 2025 09:16:39 -0800 (PST) From: Kurt Borja Date: Fri, 21 Nov 2025 12:16:14 -0500 Subject: [PATCH 1/2] dt-bindings: iio: adc: Add TI ADS1018/ADS1118 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: <20251121-ads1x18-v1-1-86db080fc9a4@gmail.com> References: <20251121-ads1x18-v1-0-86db080fc9a4@gmail.com> In-Reply-To: <20251121-ads1x18-v1-0-86db080fc9a4@gmail.com> To: Jonathan Cameron , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Tobias Sperling Cc: David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Jonathan Cameron , Kurt Borja X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3845; i=kuurtb@gmail.com; h=from:subject:message-id; bh=ehm4ws2yMf2rLsIV55CO1QANq8wV/IBX6JJ6WdGosVY=; b=owGbwMvMwCUmluBs8WX+lTTG02pJDJkK84rZI985W77sTwyJeH1Fk7uSsyT7kHT21ZbWAo03S qYPlrl2lLIwiHExyIopsrQnLPr2KCrvrd+B0Pswc1iZQIYwcHEKwETE2RgZPta6G15OFc4+Krl/ qo2Hu613n9Ab1zOzn/9T4N6zWqw3ieG/21u2H7PSeX41KJ7/xzR1rpWlcYXlqWmWxRe+nnrcqlP DBAA= X-Developer-Key: i=kuurtb@gmail.com; a=openpgp; fpr=54D3BE170AEF777983C3C63B57E3B6585920A69A Add documentation for Texas Instruments ADS1018 and ADS1118 analog-to-digital converters. Signed-off-by: Kurt Borja --- .../devicetree/bindings/iio/adc/ti,ads1118.yaml | 132 +++++++++++++++++= ++++ 1 file changed, 132 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1118.yaml b/Do= cumentation/devicetree/bindings/iio/adc/ti,ads1118.yaml new file mode 100644 index 000000000000..eb7228ed6ddb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1118.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1118.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI ADS1018/ADS1118 SPI analog to digital converter + +maintainers: + - Kurt Borja + +description: | + The ADS1018/ADS1118 is a precision, low-power, 12-bit or 16-bit, noise-f= ree, + analog-to-digital converter (ADC). It integrates a programmable gain amp= lifier + (PGA), voltage reference, oscillator and high-accuracy temperature senso= r. + + Datasheets: + - ADS1018: https://www.ti.com/lit/ds/symlink/ads1018.pdf + - ADS1118: https://www.ti.com/lit/ds/symlink/ads1118.pdf + +properties: + compatible: + enum: + - ti,ads1018 + - ti,ads1118 + + reg: + maxitems: 1 + + interrupts: + description: DOUT/DRDY (Data Out/Data Ready) line. + maxitems: 1 + + drdy-gpios: + description: + Extra GPIO line connected to DOUT/DRDY (Data Out/Data Ready). This a= llows + distinguishing between latched and real DRDY IRQs. + maxitems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + '#io-channel-cells': + const: 1 + +required: + - compatible + - reg + - drdy-gpios + - '#address-cells' + - '#size-cells' + +patternProperties: + "^channel@[0-7]$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: Properties for a single ADC channel. + + properties: + reg: + minimum: 0 + maximum: 7 + description: The channel index (0-7). + + ti,gain: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: + Programmable gain amplifier configuration, as described in the P= GA + Config Register Field description. If not present, the default is + used. + + ti,datarate: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 7 + description: + Data rate configuration, as described in the DR Config Register = Field + description. If not present, the default is used. + + required: + - reg + + additionalProperties: false + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + properties: + compatible: + contains: + const: ti,ads1018 + then: + patternProperties: + "^channel@[0-7]$": + properties: + ti,datarate: + maximum: 6 + +unevaluatedProperties: false + +examples: + - | + spi0 { + #address-cells =3D <1>; + #size-cells =3D <0>; + + ads1118@0 { + compatible =3D "ti,ads1118"; + reg =3D <0>; + + #address-cells =3D <1>; + #size-cells =3D <0>; + + spi-max-frequency =3D <4000000>; + spi-cpha; + + interrupts-extended =3D <&gpio 14 IRQ_TYPE_EDGE_FALLING>; + drdy-gpios =3D <&gpio 14 GPIO_ACTIVE_LOW>; + + channel@4 { + reg =3D <4>; + ti,gain =3D <0>; + ti,datarate =3D <7>; + }; + }; + }; --=20 2.52.0 From nobody Tue Dec 2 01:06:07 2025 Received: from mail-vs1-f50.google.com (mail-vs1-f50.google.com [209.85.217.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C2BCF34BA31 for ; Fri, 21 Nov 2025 17:16:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.217.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763745405; cv=none; b=bxSsJXiRhX1uouAwl/xE96uguzkapSdYAnbr+2slwoKVndtE0ydmTRqwbGS7JDyHYFhujWr9esimFgezx8EzkAmk57x7DD2igmMG4VEAc0wBvGHPHFNF3jWoFz9kb90UjOyG8i7SCSyZeDv43RQUoLuBz+d4VSyY/UhvoZhR/m8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763745405; c=relaxed/simple; bh=UF2wDSXDzdfTq1Rw9rtONQScjJgxARppg4hE6+jSh70=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=VgQsLs24yFV+6Dp8uYZVEFfQXGQyFwEzV6F5cUyQeNK8c22A4f6mtwjdTwN1FbPfdRc4CkI6axZW2Ci0n0AL/hoN51WN2sQlwBrSok88LrQVHAL+hHEG67FthVFRsmNVPJqzx1vqVEblVC5/IyizzReOgvVuI96CqX2jelIgn5c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=g6GoePd/; arc=none smtp.client-ip=209.85.217.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="g6GoePd/" Received: by mail-vs1-f50.google.com with SMTP id ada2fe7eead31-5e18598b9b1so1548531137.0 for ; Fri, 21 Nov 2025 09:16:42 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1763745401; x=1764350201; 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=vnHfYjIl9o2rW2uHMzTk7sYv5nKhTDRnLuQxYNOwLUk=; b=g6GoePd/L1aYyP+I/XqV6QiFgKgZpMQGoze0McEuNXBumPK9JI70edLWEJAGiZuJAr SAOdIuI5HwR2c6FjHIOijEf+u3OPkhe4bEWFjAPnOAir8EKHEVc4geb/gyx8JJZ1Zo+N MPICYL19fMibuM4WqsH0H1SA8CFLovYxV6RehTDwYDyqxy4Pr46mhuPRg9EEQgWORAeU lPPHnaLeQzhizcSLCtrAghP3c7bzoxpSgHhWYGqHWwVKup0rN9HaQbTVCCCNU1ychwKZ GOJALGKmCP7xA6LaYDNuQ/C/BTkkNCc1P7jra8fk0vvaBoxYo43E4muMJ89nLle74xQC vJrw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763745401; x=1764350201; 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=vnHfYjIl9o2rW2uHMzTk7sYv5nKhTDRnLuQxYNOwLUk=; b=Azy0iAsvYvBsMNw4ygnPvOz+cCNkyBTonXGJo8iBOMyp8OhosIERss/cFm10F6ib8J hvckKwjJovY2nd74JoRpdmtvN0TMPyKxTEZ/7jsCW4l0wIkiXxDJX8ihzJ9AbxOX7XQx hDKL80u0B8amecUFEC1V3yeAN7fQRi0YfAWw2/eMgSdJDuDIr2uEkKhxqjYSO8WJ13K0 Xc/Es/oonaIKueoJi301IcIObjzPWzE1/TE6WbocjAgLoH5AV6amJWVqJ65TEm7KEJnH K79P/ocn0+ioGosvbCXExee9N8YWPXExcqE+t10JuTfBzA0UJrU7wehBWTxWiGsDRoPo e+rQ== X-Forwarded-Encrypted: i=1; AJvYcCXrbSynFZx36DTtQ6xDbGdFlep338Ba46TfheDYMjSNF5/FWJdstEU4UoGWILBc59uOWXbQzwM5aF/HzFA=@vger.kernel.org X-Gm-Message-State: AOJu0YyuDX52t/6mlTn+MxwaUAlvxRQG3hxtvieNp/aR1mddLImuwzg7 mxGiRnx00DJCKiswOKmoTTIGClH/5rzj5Zuij7BiWLrUecwE+EO2/qWx X-Gm-Gg: ASbGncuSUYSeK2MU/8dTIvigYsyznb2hgT138zSd2v1EZbMmiHG/1JEyUIQNOxhJ21m ywoPmU9P+1wQzq5W0twVfLg3toIBRz0FDnE4Mw5ReOj/51OFDfKPr9FGxB0JtzTSnrZy+Ly5xLc LKuVf4iIdA0xYE8XKutF9J5y3mKqz+7mqiWjfeZKfq+Ae28J/qjBym/HF5smTA/xKDgipmkKnMo fmARuiYc278v+UVg6AqrMCkD2DEWCeEupN7E5x8gtvN6pCpNYucBonQOIxNBzHa53xtqvfltPMt 6wqtwm0rqZHyzQxoeRPhxECbZQQI9QpYSCjBYCR6Mq3W8MoFinny6x85E7HYQIkqa3CBDH6r628 dY7v2/5WSHi8LU7baaXa/pIuifzPoEOyzfU403Odgb/deBmO8/+JsvsU02LqHt/9wZjgqPTxw6g daeLO7A0Xake6v X-Google-Smtp-Source: AGHT+IEwCQLLvz6bXrnndp79IRQ6McHHOURui74acWnIERKOaM88Hr+7LWgS0C/oN8N2m2+eg/PEfw== X-Received: by 2002:a05:6102:c52:b0:5dd:b2ee:4423 with SMTP id ada2fe7eead31-5e1de088196mr1195058137.11.1763745401450; Fri, 21 Nov 2025 09:16:41 -0800 (PST) Received: from [192.168.100.70] ([2800:bf0:82:3d2:875c:6c76:e06b:3095]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-93c562728fdsm2554368241.7.2025.11.21.09.16.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 21 Nov 2025 09:16:41 -0800 (PST) From: Kurt Borja Date: Fri, 21 Nov 2025 12:16:15 -0500 Subject: [PATCH 2/2] iio: adc: Add ti-ads1x18 driver 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: <20251121-ads1x18-v1-2-86db080fc9a4@gmail.com> References: <20251121-ads1x18-v1-0-86db080fc9a4@gmail.com> In-Reply-To: <20251121-ads1x18-v1-0-86db080fc9a4@gmail.com> To: Jonathan Cameron , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Tobias Sperling Cc: David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Jonathan Cameron , Kurt Borja X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=29392; i=kuurtb@gmail.com; h=from:subject:message-id; bh=UF2wDSXDzdfTq1Rw9rtONQScjJgxARppg4hE6+jSh70=; b=owGbwMvMwCUmluBs8WX+lTTG02pJDJkK84rfr2pbYWQzwVJK5rjT8mk8a85sMJXV2S83Uf/u1 mOqCgy3OkpZGMS4GGTFFFnaExZ9exSV99bvQOh9mDmsTCBDGLg4BWAiKRaMDDO6BNO7Dlz7f8Lv /6bbc2537f40RWOjhNgl8cuZOYz9hX8ZGWZyCvMlZs3gCb28O2qR7zOX2bECOyqrJzwR+x/z8uR pZQ4A X-Developer-Key: i=kuurtb@gmail.com; a=openpgp; fpr=54D3BE170AEF777983C3C63B57E3B6585920A69A Add ti-ads1x18 driver for Texas Instruments ADS1018 and ADS1118 SPI analog-to-digital converters. These devices support a data-ready IRQ, which is shared with the MOSI line. Due to this peculiarity, interrupt and IIO trigger design is heavily inspired in ad_sigma_delta drivers. The IRQ is only enabled when waiting for data and an additional GPIO is needed to check if it isn't a latched pending interrupt. Signed-off-by: Kurt Borja --- MAINTAINERS | 7 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads1x18.c | 919 +++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 939 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 31d98efb1ad1..f9f0983d5d6f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25646,6 +25646,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml F: drivers/iio/adc/ti-ads1119.c =20 +TI ADS1X18 ADC DRIVER +M: Kurt Borja +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads1118.yaml +F: drivers/iio/adc/ti-ads1x18.c + TI ADS7924 ADC DRIVER M: Hugo Villeneuve L: linux-iio@vger.kernel.org diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58da8255525e..0d3229a67af8 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1686,6 +1686,18 @@ config TI_ADS1119 This driver can also be built as a module. If so, the module will= be called ti-ads1119. =20 +config TI_ADS1X18 + tristate "Texas Instruments ADS1119 ADC" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1X18 + ADC chips. + + This driver can also be built as a module. If so, the module will= be + called ti-ads1x18. + config TI_ADS124S08 tristate "Texas Instruments ADS124S08" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7cc8f9a12f76..ab3b52307482 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -147,6 +147,7 @@ obj-$(CONFIG_TI_ADC161S626) +=3D ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) +=3D ti-ads1015.o obj-$(CONFIG_TI_ADS1100) +=3D ti-ads1100.o obj-$(CONFIG_TI_ADS1119) +=3D ti-ads1119.o +obj-$(CONFIG_TI_ADS1X18) +=3D ti-ads1x18.o obj-$(CONFIG_TI_ADS124S08) +=3D ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) +=3D ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) +=3D ti-ads131e08.o diff --git a/drivers/iio/adc/ti-ads1x18.c b/drivers/iio/adc/ti-ads1x18.c new file mode 100644 index 000000000000..1bf4fe34a825 --- /dev/null +++ b/drivers/iio/adc/ti-ads1x18.c @@ -0,0 +1,919 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Texas Instruments ADS1X18 ADC driver + * + * Copyright (C) 2025 Kurt Borja + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ADS1X18_CFG_DEFAULT 0x058b + +#define ADS1X18_CFG_OS_TRIG BIT_U16(15) +#define ADS1X18_CFG_TS_MODE_EN BIT_U16(4) +#define ADS1X18_CFG_PULL_UP BIT_U16(3) +#define ADS1X18_CFG_NOP BIT_U16(1) +#define ADS1X18_CFG_VALID (ADS1X18_CFG_PULL_UP | ADS1X18_CFG_NOP) + +#define ADS1X18_CFG_MUX_MASK GENMASK_U16(14, 12) +#define ADS1X18_AIN0_AIN1_ADDR 0 +#define ADS1X18_AIN0_AIN3_ADDR 1 +#define ADS1X18_AIN1_AIN3_ADDR 2 +#define ADS1X18_AIN2_AIN3_ADDR 3 +#define ADS1X18_AIN0_GND_ADDR 4 +#define ADS1X18_AIN1_GND_ADDR 5 +#define ADS1X18_AIN2_GND_ADDR 6 +#define ADS1X18_AIN3_GND_ADDR 7 +#define ADS1X18_TEMP_ADDR 8 +#define ADS1X18_TIMESTAMP_ADDR 9 + +#define ADS1X18_CFG_PGA_MASK GENMASK_U16(11, 9) +#define ADS1X18_PGA_MODE_0 0 +#define ADS1X18_PGA_MODE_1 1 +#define ADS1X18_PGA_MODE_2 2 +#define ADS1X18_PGA_MODE_3 3 +#define ADS1X18_PGA_MODE_4 4 +#define ADS1X18_PGA_MODE_5 5 +#define ADS1X18_PGA_DEFAULT ADS1X18_PGA_MODE_2 + +#define ADS1X18_CFG_MODE_MASK GENMASK_U16(8, 8) +#define ADS1X18_MODE_CONTINUOUS 0 +#define ADS1X18_MODE_ONESHOT 1 + +#define ADS1X18_CFG_DRATE_MASK GENMASK_U16(7, 5) +#define ADS1X18_DRATE_MODE_0 0 +#define ADS1X18_DRATE_MODE_1 1 +#define ADS1X18_DRATE_MODE_2 2 +#define ADS1X18_DRATE_MODE_3 3 +#define ADS1X18_DRATE_MODE_4 4 +#define ADS1X18_DRATE_MODE_5 5 +#define ADS1X18_DRATE_MODE_6 6 +#define ADS1X18_DRATE_MODE_7 7 +#define ADS1X18_DRATE_DEFAULT ADS1X18_DRATE_MODE_4 + +#define ADS1X18_MAX_ADC_ADDR 7 +#define ADS1X18_MAX_CHANNELS 9 + +struct ads1x18_chan_data { + unsigned int pga_mode:3; + unsigned int drate_mode:3; +}; + +struct ads1x18_chip_info { + const char *name; + + const struct iio_chan_spec *channels; + unsigned long channels_sz; + + const int *sps_table; + unsigned long sps_table_sz; + const int (*fsr_table)[2]; + unsigned long fsr_table_sz; + const int temp_scale[2]; +}; + +struct ads1x18 { + struct spi_device *spi; + struct iio_dev *indio_dev; + struct iio_trigger *indio_trig; + + struct gpio_desc *drdy_gpiod; + int drdy_irq; + + u16 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + u16 rx_buf[2]; + struct spi_transfer xfer; + struct spi_message message; + struct completion data_ready; + struct mutex msg_lock; /* Protects message transfers */ + + unsigned int restore_mode:1; + + unsigned long bufidx_to_addr[ADS1X18_MAX_CHANNELS]; + struct ads1x18_chan_data channels[ADS1X18_MAX_CHANNELS]; + + const struct ads1x18_chip_info *chip_info; +}; + +#define ADS1X18_VOLT_CHANNEL(_addr, _chan, _chan2, _diff, _realbits) { \ + .type =3D IIO_VOLTAGE, \ + .channel =3D _chan, \ + .channel2 =3D _chan2, \ + .address =3D _addr, \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D _realbits, \ + .storagebits =3D 16, \ + .shift =3D 16 - _realbits, \ + }, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available =3D BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .modified =3D _diff, \ + .indexed =3D true, \ + .differential =3D _diff, \ +} + +#define ADS1X18_TEMP_CHANNEL(_realbits) { \ + .type =3D IIO_TEMP, \ + .channel =3D 0, \ + .address =3D ADS1X18_TEMP_ADDR, \ + .scan_type =3D { \ + .sign =3D 's', \ + .realbits =3D _realbits, \ + .storagebits =3D 16, \ + .shift =3D 16 - _realbits, \ + }, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec ads1118_iio_channels[] =3D { + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN0_AIN1_ADDR, 0, 1, true, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN0_AIN3_ADDR, 0, 3, true, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN1_AIN3_ADDR, 1, 3, true, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN2_AIN3_ADDR, 2, 3, true, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN0_GND_ADDR, 0, 0, false, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN1_GND_ADDR, 1, 0, false, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN2_GND_ADDR, 2, 0, false, 16), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN3_GND_ADDR, 3, 0, false, 16), + ADS1X18_TEMP_CHANNEL(14), + IIO_CHAN_SOFT_TIMESTAMP(ADS1X18_TIMESTAMP_ADDR), +}; + +static const struct iio_chan_spec ads1018_iio_channels[] =3D { + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN0_AIN1_ADDR, 0, 1, true, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN0_AIN3_ADDR, 0, 3, true, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN1_AIN3_ADDR, 1, 3, true, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN2_AIN3_ADDR, 2, 3, true, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN0_GND_ADDR, 0, 0, false, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN1_GND_ADDR, 1, 0, false, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN2_GND_ADDR, 2, 0, false, 12), + ADS1X18_VOLT_CHANNEL(ADS1X18_AIN3_GND_ADDR, 3, 0, false, 12), + ADS1X18_TEMP_CHANNEL(12), + IIO_CHAN_SOFT_TIMESTAMP(ADS1X18_TIMESTAMP_ADDR), +}; + +static unsigned int ads1x18_get_drate_mode(struct ads1x18 *ads1x18, + unsigned int address) +{ + return ads1x18->channels[address].drate_mode; +} + +static unsigned int ads1x18_get_pga_mode(struct ads1x18 *ads1x18, + unsigned int address) +{ + return ads1x18->channels[address].pga_mode; +} + +static void ads1x18_set_drate_mode(struct ads1x18 *ads1x18, unsigned int a= ddress, + unsigned int val) +{ + ads1x18->channels[address].drate_mode =3D val; +} + +static void ads1x18_set_pga_mode(struct ads1x18 *ads1x18, unsigned int add= ress, + unsigned int val) +{ + ads1x18->channels[address].pga_mode =3D val; +} + +static unsigned long ads1x18_calc_timeout(struct ads1x18 *ads1x18, + unsigned int drate_mode) +{ + const struct ads1x18_chip_info *chip_info =3D ads1x18->chip_info; + unsigned long timeout; + unsigned int sps; + + sps =3D chip_info->sps_table[drate_mode]; + timeout =3D DIV_ROUND_UP(MICROHZ_PER_HZ, sps); + + return usecs_to_jiffies(timeout * 2); +} + +static int __ads1x18_read_conver(struct ads1x18 *ads1x18, u16 *cnv) +{ + int ret; + + ads1x18->tx_buf[0] =3D 0; + ads1x18->tx_buf[1] =3D 0; + ret =3D spi_sync_locked(ads1x18->spi, &ads1x18->message); + if (ret) + return ret; + + *cnv =3D be16_to_cpu(ads1x18->rx_buf[0]); + + return 0; +} + +static int __ads1x18_write_config(struct ads1x18 *ads1x18, u16 cfg) +{ + ads1x18->tx_buf[0] =3D cpu_to_be16(cfg); + ads1x18->tx_buf[1] =3D 0; + + return spi_sync_locked(ads1x18->spi, &ads1x18->message); +} + +static int ads1x18_read_conver(struct ads1x18 *ads1x18, u16 *cnv) +{ + int ret; + + spi_bus_lock(ads1x18->spi->controller); + ret =3D __ads1x18_read_conver(ads1x18, cnv); + spi_bus_unlock(ads1x18->spi->controller); + + return ret; +} + +static int ads1x18_write_config(struct ads1x18 *ads1x18, u16 cfg) +{ + int ret; + + spi_bus_lock(ads1x18->spi->controller); + ret =3D __ads1x18_write_config(ads1x18, cfg); + spi_bus_unlock(ads1x18->spi->controller); + + return ret; +} + +static int ads1x18_oneshot(struct ads1x18 *ads1x18, + struct iio_chan_spec const *chan, int *val) +{ + unsigned int drate =3D ads1x18_get_drate_mode(ads1x18, chan->address); + unsigned int pga =3D ads1x18_get_pga_mode(ads1x18, chan->address); + unsigned long timeout =3D ads1x18_calc_timeout(ads1x18, drate); + u16 cnv, cfg =3D 0; + int ret; + + reinit_completion(&ads1x18->data_ready); + + cfg |=3D ADS1X18_CFG_VALID; + cfg |=3D ADS1X18_CFG_OS_TRIG; + cfg |=3D FIELD_PREP(ADS1X18_CFG_MUX_MASK, chan->address); + cfg |=3D FIELD_PREP(ADS1X18_CFG_PGA_MASK, pga); + cfg |=3D FIELD_PREP(ADS1X18_CFG_MODE_MASK, ADS1X18_MODE_ONESHOT); + cfg |=3D FIELD_PREP(ADS1X18_CFG_DRATE_MASK, drate); + if (chan->type =3D=3D IIO_TEMP) + cfg |=3D ADS1X18_CFG_TS_MODE_EN; + + ret =3D __ads1x18_write_config(ads1x18, cfg); + if (ret) + return ret; + enable_irq(ads1x18->drdy_irq); + + if (!wait_for_completion_timeout(&ads1x18->data_ready, timeout)) { + disable_irq(ads1x18->drdy_irq); + return -ETIMEDOUT; + } + disable_irq(ads1x18->drdy_irq); + + ret =3D __ads1x18_read_conver(ads1x18, &cnv); + if (ret) + return ret; + + cnv >>=3D chan->scan_type.shift; + *val =3D sign_extend32(cnv, chan->scan_type.realbits - 1); + + return ret; +} + +static int +ads1x18_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch= an, + int *val, int *val2, long mask) +{ + struct ads1x18 *ads1x18 =3D iio_priv(indio_dev); + struct ads1x18_chan_data *chan_info =3D &ads1x18->channels[chan->address]; + const struct ads1x18_chip_info *chip_info =3D ads1x18->chip_info; + int ret; + + guard(mutex)(&ads1x18->msg_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + /* CS needs to remain asserted until dataready IRQ */ + spi_bus_lock(ads1x18->spi->controller); + + ret =3D ads1x18_oneshot(ads1x18, chan, val); + + spi_bus_unlock(ads1x18->spi->controller); + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val =3D chip_info->fsr_table[chan_info->pga_mode][0]; + *val2 =3D chip_info->fsr_table[chan_info->pga_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *val =3D chip_info->temp_scale[0]; + *val2 =3D chip_info->temp_scale[1]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EOPNOTSUPP; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + *val =3D chip_info->sps_table[chan_info->drate_mode]; + return IIO_VAL_INT; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1x18_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *= chan, + const int **vals, int *type, int *length, long mask) +{ + struct ads1x18 *ads1x18 =3D iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type =3D IIO_VAL_INT_PLUS_NANO; + *vals =3D (const int *)ads1x18->chip_info->fsr_table; + *length =3D ads1x18->chip_info->fsr_table_sz * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *type =3D IIO_VAL_INT; + *vals =3D ads1x18->chip_info->sps_table; + *length =3D ads1x18->chip_info->sps_table_sz; + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1x18_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *c= han, + int val, int val2, long mask) +{ + struct ads1x18 *ads1x18 =3D iio_priv(indio_dev); + const struct ads1x18_chip_info *info =3D ads1x18->chip_info; + unsigned int i =3D 0; + + guard(mutex)(&ads1x18->msg_lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (; i < info->fsr_table_sz; i++) { + if (val !=3D info->fsr_table[i][0] || + val2 !=3D info->fsr_table[i][1]) + continue; + + ads1x18_set_pga_mode(ads1x18, chan->address, i); + return 0; + } + + return -EINVAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (; i < info->sps_table_sz; i++) { + if (val !=3D info->sps_table[i]) + continue; + + ads1x18_set_drate_mode(ads1x18, chan->address, i); + return 0; + } + + return -EINVAL; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1x18_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ads1x18_iio_info =3D { + .read_raw =3D ads1x18_read_raw, + .read_avail =3D ads1x18_read_avail, + .write_raw =3D ads1x18_write_raw, + .write_raw_get_fmt =3D ads1x18_write_raw_get_fmt, +}; + +static int ads1x18_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct ads1x18 *ads1x18 =3D iio_trigger_get_drvdata(trig); + u16 cnv; + + guard(mutex)(&ads1x18->msg_lock); + + /* + * We need to lock the SPI bus when enabling the trigger to prevent + * another device from taking the CS and DOUT/DRDY lines. + */ + + if (state) { + spi_bus_lock(ads1x18->spi->controller); + enable_irq(ads1x18->drdy_irq); + + /* + * Read once to ensure we are holding the CS line after locking + */ + return __ads1x18_read_conver(ads1x18, &cnv); + } + + disable_irq(ads1x18->drdy_irq); + spi_bus_unlock(ads1x18->spi->controller); + + return 0; +} + +static const struct iio_trigger_ops ads1x18_trigger_ops =3D { + .set_trigger_state =3D ads1x18_set_trigger_state, + .validate_device =3D iio_trigger_validate_own_device, +}; + +static int ads1x18_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1x18 *ads1x18 =3D iio_priv(indio_dev); + unsigned int pga, drate, addr, idx; + u16 cfg =3D 0; + + guard(mutex)(&ads1x18->msg_lock); + + idx =3D find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + addr =3D ads1x18->bufidx_to_addr[idx]; + pga =3D ads1x18_get_pga_mode(ads1x18, addr); + drate =3D ads1x18_get_drate_mode(ads1x18, addr); + + cfg |=3D ADS1X18_CFG_VALID; + cfg |=3D FIELD_PREP(ADS1X18_CFG_MUX_MASK, addr); + cfg |=3D FIELD_PREP(ADS1X18_CFG_PGA_MASK, pga); + cfg |=3D FIELD_PREP(ADS1X18_CFG_MODE_MASK, ADS1X18_MODE_CONTINUOUS); + cfg |=3D FIELD_PREP(ADS1X18_CFG_DRATE_MASK, drate); + if (addr =3D=3D ADS1X18_TEMP_ADDR) + cfg |=3D ADS1X18_CFG_TS_MODE_EN; + + return ads1x18_write_config(ads1x18, cfg); +} + +static int ads1x18_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1x18 *ads1x18 =3D iio_priv(indio_dev); + + guard(mutex)(&ads1x18->msg_lock); + + return ads1x18_write_config(ads1x18, ADS1X18_CFG_DEFAULT); +} + +static bool ads1x18_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + return bitmap_weight(mask, iio_get_masklength(indio_dev)) =3D=3D 1; +} + +static const struct iio_buffer_setup_ops ads1x18_buffer_ops =3D { + .preenable =3D ads1x18_buffer_preenable, + .postdisable =3D ads1x18_buffer_postdisable, + .validate_scan_mask =3D ads1x18_validate_scan_mask, +}; + +static irqreturn_t ads1x18_irq_handler(int irq, void *dev_id) +{ + struct ads1x18 *ads1x18 =3D dev_id; + + /* + * We need to check if the "drdy" pin is actually active or if it's a + * latched pending interrupt. + */ + if (!gpiod_get_value(ads1x18->drdy_gpiod)) + return IRQ_HANDLED; + + complete(&ads1x18->data_ready); + iio_trigger_poll(ads1x18->indio_trig); + + return IRQ_HANDLED; +} + +static int ads1x18_interrupt_init(struct ads1x18 *ads1x18) +{ + const struct ads1x18_chip_info *info =3D ads1x18->chip_info; + struct spi_device *spi =3D ads1x18->spi; + + ads1x18->drdy_gpiod =3D devm_gpiod_get(&spi->dev, "drdy", GPIOD_IN); + if (IS_ERR(ads1x18->drdy_gpiod)) + return dev_err_probe(&spi->dev, PTR_ERR(ads1x18->drdy_gpiod), + "Failed to get 'drdy' GPIO.\n"); + + ads1x18->drdy_irq =3D gpiod_to_irq(ads1x18->drdy_gpiod); + if (ads1x18->drdy_irq < 0) + return dev_err_probe(&spi->dev, ads1x18->drdy_irq, + "Failed to get 'drdy IRQ.\n'"); + + /* + * The "data-ready" IRQ line is shared with the MOSI pin, thus we need + * to keep it disabled until we actually request data. + */ + return devm_request_irq(&spi->dev, ads1x18->drdy_irq, + ads1x18_irq_handler, IRQF_NO_AUTOEN, + info->name, ads1x18); +} + +static irqreturn_t ads1x18_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct ads1x18 *ads1x18 =3D iio_priv(indio_dev); + struct { + u16 conv; + aligned_s64 ts; + } scan; + int ret; + + guard(mutex)(&ads1x18->msg_lock); + + if (iio_trigger_using_own(indio_dev)) { + disable_irq(ads1x18->drdy_irq); + ret =3D __ads1x18_read_conver(ads1x18, &scan.conv); + enable_irq(ads1x18->drdy_irq); + } else { + ret =3D ads1x18_read_conver(ads1x18, &scan.conv); + } + + if (ret) + return IRQ_HANDLED; + + ret =3D iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + if (!ret) + iio_trigger_notify_done(ads1x18->indio_trig); + + return IRQ_HANDLED; +} + +static int ads1x18_triggered_buffer_init(struct ads1x18 *ads1x18) +{ + struct iio_dev *indio_dev =3D ads1x18->indio_dev; + struct spi_device *spi =3D ads1x18->spi; + int ret; + + ads1x18->indio_trig =3D devm_iio_trigger_alloc(&spi->dev, "%s-dev%d-drdy", + indio_dev->name, + iio_device_id(indio_dev)); + if (!ads1x18->indio_trig) + return -ENOMEM; + + iio_trigger_set_drvdata(ads1x18->indio_trig, ads1x18); + ads1x18->indio_trig->ops =3D &ads1x18_trigger_ops; + + ret =3D devm_iio_trigger_register(&spi->dev, ads1x18->indio_trig); + if (ret) + return ret; + + return devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + iio_pollfunc_store_time, + ads1x18_trigger_handler, + &ads1x18_buffer_ops); +} + +static int ads1x18_message_init(struct ads1x18 *ads1x18) +{ + struct spi_device *spi =3D ads1x18->spi; + + /* + * We need to keep CS asserted to catch "data-ready" interrupts. + * Otherwise the DOUT/DRDY line enters a Hi-Z state and it can't be + * driven by the ADC. + */ + ads1x18->xfer.cs_change =3D 1; + ads1x18->xfer.tx_buf =3D ads1x18->tx_buf; + ads1x18->xfer.rx_buf =3D ads1x18->rx_buf; + ads1x18->xfer.len =3D sizeof(ads1x18->tx_buf); + spi_message_init_no_memset(&ads1x18->message); + spi_message_add_tail(&ads1x18->xfer, &ads1x18->message); + + return devm_spi_optimize_message(&spi->dev, spi, &ads1x18->message); +} + +static int ads1x18_fill_properties(struct ads1x18 *ads1x18, + struct fwnode_handle *handle, + struct iio_chan_spec *chan) +{ + const struct ads1x18_chip_info *info =3D ads1x18->chip_info; + struct ads1x18_chan_data *chan_data; + u32 val, reg; + int ret; + + ret =3D fwnode_property_read_u32(handle, "reg", ®); + if (ret) + return ret; + if (reg > ADS1X18_MAX_ADC_ADDR) + return dev_err_probe(&ads1x18->spi->dev, -ENXIO, + "%s: Invalid channel address %u.\n", + fwnode_get_name(handle), reg); + + *chan =3D info->channels[reg]; + + chan_data =3D &ads1x18->channels[reg]; + chan_data->pga_mode =3D ADS1X18_PGA_DEFAULT; + chan_data->drate_mode =3D ADS1X18_DRATE_DEFAULT; + + if (fwnode_property_present(handle, "ti,gain")) { + ret =3D fwnode_property_read_u32(handle, "ti,gain", &val); + if (ret) + return ret; + if (val >=3D info->fsr_table_sz) + return dev_err_probe(&ads1x18->spi->dev, -ENXIO, + "%s: ti,gain not in range.", + fwnode_get_name(handle)); + + chan_data->pga_mode =3D val; + } + + if (fwnode_property_present(handle, "ti,datarate")) { + ret =3D fwnode_property_read_u32(handle, "ti,datarate", &val); + if (ret) + return ret; + if (val >=3D info->sps_table_sz) + return dev_err_probe(&ads1x18->spi->dev, -ENXIO, + "%s: ti,datarate not in range.", + fwnode_get_name(handle)); + + chan_data->drate_mode =3D val; + } + + return 0; +} + +static int ads1x18_channels_init(struct ads1x18 *ads1x18, + const struct ads1x18_chip_info *info, + struct iio_chan_spec **cs) +{ + struct device *dev =3D &ads1x18->spi->dev; + struct iio_chan_spec *channels; + int ret, nchans, index =3D 0; + + nchans =3D device_get_named_child_node_count(dev, "channel"); + if (!nchans) + return dev_err_probe(dev, -ENODEV, + "No ADC channels described.\n"); + + channels =3D devm_kcalloc(dev, nchans + 2, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + device_for_each_named_child_node_scoped(dev, child, "channel") { + ret =3D ads1x18_fill_properties(ads1x18, child, &channels[index]); + if (ret) + return ret; + + channels[index].scan_index =3D index; + ads1x18->bufidx_to_addr[index] =3D channels[index].address; + index++; + } + + ads1x18->channels[ADS1X18_TEMP_ADDR].drate_mode =3D ADS1X18_DRATE_DEFAULT; + channels[index] =3D info->channels[ADS1X18_TEMP_ADDR]; + channels[index].scan_index =3D index; + ads1x18->bufidx_to_addr[index] =3D channels[index].address; + index++; + + channels[index] =3D info->channels[ADS1X18_TIMESTAMP_ADDR]; + ads1x18->bufidx_to_addr[index] =3D channels[index].address; + + *cs =3D channels; + + return index; +} + +static int ads1x18_spi_probe(struct spi_device *spi) +{ + const struct ads1x18_chip_info *info =3D spi_get_device_match_data(spi); + struct iio_chan_spec *channels; + struct iio_dev *indio_dev; + struct ads1x18 *ads1x18; + int num_channels, ret; + + indio_dev =3D devm_iio_device_alloc(&spi->dev, sizeof(*ads1x18)); + if (!indio_dev) + return -ENOMEM; + + ads1x18 =3D iio_priv(indio_dev); + ads1x18->spi =3D spi; + ads1x18->indio_dev =3D indio_dev; + ads1x18->chip_info =3D info; + mutex_init(&ads1x18->msg_lock); + init_completion(&ads1x18->data_ready); + spi_set_drvdata(spi, ads1x18); + + num_channels =3D ads1x18_channels_init(ads1x18, info, &channels); + if (num_channels < 0) + return num_channels; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->name =3D info->name; + indio_dev->info =3D &ads1x18_iio_info; + indio_dev->channels =3D channels; + indio_dev->num_channels =3D num_channels; + + ret =3D ads1x18_message_init(ads1x18); + if (ret) + return ret; + + ret =3D ads1x18_triggered_buffer_init(ads1x18); + if (ret) + return ret; + + ret =3D ads1x18_interrupt_init(ads1x18); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static int ads1x18_suspend(struct device *dev) +{ + struct ads1x18 *ads1x18 =3D dev_get_drvdata(dev); + u16 cfg; + int ret; + + guard(mutex)(&ads1x18->msg_lock); + + /* Current config is readback into rx_buf[1] */ + cfg =3D be16_to_cpu(ads1x18->rx_buf[1]); + if (FIELD_GET(ADS1X18_CFG_MODE_MASK, cfg) =3D=3D ADS1X18_MODE_ONESHOT) + return 0; + + cfg |=3D FIELD_PREP(ADS1X18_CFG_MODE_MASK, ADS1X18_MODE_ONESHOT); + ret =3D ads1x18_write_config(ads1x18, cfg); + if (ret) + return ret; + + ads1x18->restore_mode =3D 1; + + return 0; +} + +static int ads1x18_resume(struct device *dev) +{ + struct ads1x18 *ads1x18 =3D dev_get_drvdata(dev); + u16 cfg; + int ret; + + guard(mutex)(&ads1x18->msg_lock); + + if (!ads1x18->restore_mode) + return 0; + + cfg =3D be16_to_cpu(ads1x18->rx_buf[1]); + FIELD_MODIFY(ADS1X18_CFG_MODE_MASK, &cfg, ADS1X18_MODE_CONTINUOUS); + ret =3D ads1x18_write_config(ads1x18, cfg); + if (ret) + return ret; + + ads1x18->restore_mode =3D 0; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ads1x18_pm_ops, ads1x18_suspend, ads1x18_r= esume); + +static const int ads1118_fsr_table[][2] =3D { + [ADS1X18_PGA_MODE_0] =3D { 0, 187500 }, + [ADS1X18_PGA_MODE_1] =3D { 0, 125000 }, + [ADS1X18_PGA_MODE_2] =3D { 0, 62500 }, + [ADS1X18_PGA_MODE_3] =3D { 0, 31250 }, + [ADS1X18_PGA_MODE_4] =3D { 0, 15625 }, + [ADS1X18_PGA_MODE_5] =3D { 0, 7812 } +}; + +static const int ads1018_fsr_table[][2] =3D { + [ADS1X18_PGA_MODE_0] =3D { 0, 3000000 }, + [ADS1X18_PGA_MODE_1] =3D { 0, 2000000 }, + [ADS1X18_PGA_MODE_2] =3D { 0, 1000000 }, + [ADS1X18_PGA_MODE_3] =3D { 0, 500000 }, + [ADS1X18_PGA_MODE_4] =3D { 0, 250000 }, + [ADS1X18_PGA_MODE_5] =3D { 0, 125000 } +}; + +static const unsigned int ads1018_drate_table[] =3D { + [ADS1X18_DRATE_MODE_0] =3D 128, + [ADS1X18_DRATE_MODE_1] =3D 250, + [ADS1X18_DRATE_MODE_2] =3D 490, + [ADS1X18_DRATE_MODE_3] =3D 920, + [ADS1X18_DRATE_MODE_4] =3D 1600, + [ADS1X18_DRATE_MODE_5] =3D 2400, + [ADS1X18_DRATE_MODE_6] =3D 3300 +}; + +static const unsigned int ads1118_drate_table[] =3D { + [ADS1X18_DRATE_MODE_0] =3D 8, + [ADS1X18_DRATE_MODE_1] =3D 16, + [ADS1X18_DRATE_MODE_2] =3D 32, + [ADS1X18_DRATE_MODE_3] =3D 64, + [ADS1X18_DRATE_MODE_4] =3D 128, + [ADS1X18_DRATE_MODE_5] =3D 250, + [ADS1X18_DRATE_MODE_6] =3D 475, + [ADS1X18_DRATE_MODE_7] =3D 860 +}; + +static const struct ads1x18_chip_info ads1018_chip_info =3D { + .name =3D "ads1018", + + .channels =3D ads1018_iio_channels, + .channels_sz =3D ARRAY_SIZE(ads1018_iio_channels), + + .fsr_table =3D ads1018_fsr_table, + .fsr_table_sz =3D ARRAY_SIZE(ads1018_fsr_table), + .sps_table =3D ads1018_drate_table, + .sps_table_sz =3D ARRAY_SIZE(ads1018_drate_table), + .temp_scale =3D { 0, 125000 }, +}; + +static const struct ads1x18_chip_info ads1118_chip_info =3D { + .name =3D "ads1118", + + .channels =3D ads1118_iio_channels, + .channels_sz =3D ARRAY_SIZE(ads1118_iio_channels), + + .fsr_table =3D ads1118_fsr_table, + .fsr_table_sz =3D ARRAY_SIZE(ads1118_fsr_table), + .sps_table =3D ads1118_drate_table, + .sps_table_sz =3D ARRAY_SIZE(ads1118_drate_table), + .temp_scale =3D { 0, 31250 }, +}; + +static const struct of_device_id ads1x18_of_match[] =3D { + { .compatible =3D "ti,ads1018", .data =3D &ads1018_chip_info }, + { .compatible =3D "ti,ads1118", .data =3D &ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1x18_of_match); + +static const struct spi_device_id ads1x18_spi_match[] =3D { + { "ads1018", (kernel_ulong_t)&ads1018_chip_info }, + { "ads1118", (kernel_ulong_t)&ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1x18_spi_match); + +static struct spi_driver ads1x18_spi_driver =3D { + .driver =3D { + .name =3D "ads1x18", + .of_match_table =3D ads1x18_of_match, + .pm =3D pm_sleep_ptr(&ads1x18_pm_ops), + }, + .probe =3D ads1x18_spi_probe, + .id_table =3D ads1x18_spi_match, +}; + +module_spi_driver(ads1x18_spi_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1X18 ADC Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja "); --=20 2.52.0