From patchwork Fri Aug 30 11:49:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Sperling, Tobias" X-Patchwork-Id: 13785021 Received: from FR5P281CU006.outbound.protection.outlook.com (mail-germanywestcentralazon11022134.outbound.protection.outlook.com [40.107.149.134]) (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 C5C11170A01; Fri, 30 Aug 2024 11:49:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.149.134 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725018601; cv=fail; b=JaHoU96si9Sk4W6dvdbneJ8Iugesbqnvoiklq+TpDsNUblDlnLTanSknpRb52EXGKouOx2n+i0VTNldfFvxqDc8jlrUmEzEUBRgP0VtYl4l6K8VBbVZ5NN0bkot3t/e4qfl8QkH+qJv4XCqOHUGh4UUHU+QqrK78tOou/yjtJnQ= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725018601; c=relaxed/simple; bh=mp8KqmPVjYY5HQzruIpwFh6Ey5YjF5BvF/QPV880OVg=; h=From:To:CC:Subject:Date:Message-ID:Content-Type:MIME-Version; b=liEuunTiTwVN4tIC85YGd3SAQpv8ceyp42fUfmC3v1B7BK9f6BO1EYeDk3OYTv+z/0kYRUoYIoWCRojoF0dI+lO2gH2/uAJ2ahnChaut/ABOR9fmmTWay2JeEMhajo3JrKUCjhQSMOTnz7Slnexz2kOR/0t3w0HDZq0NF7/95gQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=Softing.com; spf=pass smtp.mailfrom=Softing.com; dkim=pass (2048-bit key) header.d=softing.com header.i=@softing.com header.b=Ec5+5DmT; arc=fail smtp.client-ip=40.107.149.134 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=Softing.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=Softing.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=softing.com header.i=@softing.com header.b="Ec5+5DmT" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=QW3i5nFCoZv3XPUHdUouwAMj+rs+vBjlG3aMnrRjtI5/PTDAjV0FVwO9P65178znL3/8LabmpFWQcWWU2cFcJWfIuf8Zc9H42fzoSmzEoAgdxWMZXGOXREEDgx+qFukOriGVZwiiDD2B62CcQaRonQAJ3EqdxJMaGESLLrz0sOcuFA+PNqKlR2+i4Q7yZcm/5QUAvzUAMYVHdKT9++iAitRCsPDJsdIYeu6Uxw3/CUl+J9TybOI/5XPERQNsAhI+zKwQRVJJidwfK98FQkOvHuqFsVyCUaslE6DcSOqfzDfov+tTwB02h/lVLHWovytbkUiwE1/XYXXGxLcIbVzFbg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=EemrGTTmrZgaozT/sfY3WA1Qdii7epm7DQ3Rv/82ues=; b=uaEA20yKtWwYwJFsCp5X1GEpNSFocGTxOyYyvSwCjS/XEN9xmY5OOG8bOMefV4kxVqCLf/jBLtNoO1lo3Ncp18A4R3sXj+TyRviEe5k1uZ9qD+AWCavj2R1lUnP8hzFEIPxjLXxGNPe5k50xH2Ecw/NyO7UJhVho6kaLjvVpDWCQo1Mkg13UJmX5+vA85ZAGK/N8jyOrI/2C3nLYsNd2ODUVHmnG9nwP4KVjLMr+6hhR8mJbWCZG5+guNnYXlBfYK4lneFhwDSZ7niwRsrNd4q9nmQbGwDZiimhxVrjtl+ZgrJFVxMDYH5o/BA7Nm9qOiGXB1yOx7FC6hMtNaWdfvg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=softing.com; dmarc=pass action=none header.from=softing.com; dkim=pass header.d=softing.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=softing.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=EemrGTTmrZgaozT/sfY3WA1Qdii7epm7DQ3Rv/82ues=; b=Ec5+5DmTaRf+zLSMb7Yiy4STmDmQIpgDNgSYuNjzaAMhyMni4HEXscmoD9N0vYLHMeu4aGgk2kX2gc1Vbtan7CnpJrm39gJtFQkp/DVIoMiIViwTLv7kMdZ8IojMFXCkDZDureGgaR9vUVA5AvKWcu/97I7Rwr7kxpxf9iMZKfEXa/CvUHHUvp+LD1Iz+YYKT17qaBqebT9KEDBwVoNs1tqI3yuI9DrIRRUHZ26tskqykprzb/JdDTGrWCjZa7Lhp1MVM9wIgnu7Lmvr92AUDvuyYcpDyoqMZjjLvyXfupW8WEORmARKmdn5EhcX+59icE8HdwjU3yRqhCX85g5pXg== Received: from BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM (2603:10a6:b10:43::7) by FR5P281MB4123.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:106::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7897.28; Fri, 30 Aug 2024 11:49:53 +0000 Received: from BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM ([fe80::8de2:b2ba:4092:939a]) by BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM ([fe80::8de2:b2ba:4092:939a%6]) with mapi id 15.20.7918.019; Fri, 30 Aug 2024 11:49:53 +0000 From: "Sperling, Tobias" To: "linux-kernel@vger.kernel.org" , "linux-hwmon@vger.kernel.org" , "devicetree@vger.kernel.org" , "linux-doc@vger.kernel.org" CC: "jdelvare@suse.com" , "linux@roeck-us.net" , "robh@kernel.org" , "krzk+dt@kernel.org" , "conor+dt@kernel.org" , "corbet@lwn.net" , "Sperling, Tobias" Subject: [PATCH 1/2] dt-bindings: hwmon: Introduce ADS71x8 Thread-Topic: [PATCH 1/2] dt-bindings: hwmon: Introduce ADS71x8 Thread-Index: Adr60bwOPjoiJD3QTreu2+tBMAOlGg== Date: Fri, 30 Aug 2024 11:49:53 +0000 Message-ID: Accept-Language: de-DE, en-US Content-Language: de-DE X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=Softing.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: BE1P281MB2420:EE_|FR5P281MB4123:EE_ x-ms-office365-filtering-correlation-id: 15995782-8292-4e7d-6e6f-08dcc8e9dc9f x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|1800799024|366016|376014|7416014|38070700018; x-microsoft-antispam-message-info: L+02nldvPKSnq0IkiWMVs3e0jh8waoyn5bRs5h0eMeEiWAmka9VbkeD4d3eEKvFJZXnPj+O6LSKvoDPmxXoOKI3QlxME38DAbdb60ZOs+rLrakzSD8ArauEo6SFliBXNQg7C9PSvIkoaQO26ViNl56xLPG0tM4k872KAjoZ9SMUU8ZiBLZMDBr2R/HWNRcL44uUJOUXEsZsHxA5bLNYdKtPTp8iWO+iauZMsqSiUPQ4e25aBGxhutVrGnYM5bNFT4qbko+PTXQXh/Kflwx+To/KM9HGlx4A2EnC72WsfpWVyPfeoRuUujFIgYy+PuwjTRHFOJVu0akBObnCqPQTsanRj+fbaQHeWbpCxj5VMg+CS7wVTs6eP7vwHgybQ5ye80fReX3yUxh3tQ072ElFNzlqDfkSLfJ4TBmmdRWQRCo7OoHHCcEFmyoKYpTLqH7nXc+uXIjrqnaxt3QvTCysJ535hS8iN58k1He7zPg5QDQuNmVEk4KWB+A3almIHqmAfz+4EZCsBT30wjg4j05J0L1/1C4spuVYoaCRwVEkbbpicy4VV5DVF8sIFEz1ckw35R4f5MmM7UH2dLT++U99z6b/ow6/VkYFu5+k1hdQvH4847ZLZlnjLZeOlA8WNWExFHqEj2HLtBK4mQ7jWnyuFeIZkQVGFfaCDLbknBXD9sifSGXBcLyO91ZvgUpapmpOiiA71QOOY+HneunBM+8PXDmvgIU0S321NZn45jyT6um4/Dnftx2ojgn7BDK3tl/3wVgIFIYBG1c748BN4I4chjTsjMnlPQ0BmRwJtq/5t+b0oliSDRNFP/yBV771TqOfeJ1AH5eho81LC/BdyZbOgnjDpAQembbqU9qzPgP/PhpZVO1XCWFrXzj9/QBYxv4x8jDym04eQenNpN+T62Erjpv0r+yntHdE6W6ji8G0nQfpY5YBlfKX1aKfZO9fVAeepHuYzuh7din6qoMmQ1xx4FqyZG43RBPge1pKBMUen0L8W8Efg5JrWVjNoEZKcqwbjsCg9pV3wB/OQ2+9f5kf23gzjyzfqzknvs8A9zdrjdzc5G7+fAbUaDY0J+SSuaXcLLZ42m6M+4bbD1+Sqdg5EXGB2xIncrTgzTgupZwP1KIlja10CgHnMWGX1ycsNUq1uIl8P23nThkLObeRMEBDTnHRD9lm1TV9ZMt+15f7NHL5yLz1avOKdufcp0NnK4+mS8+SnSpoHOocGAFplUt5+5KSR6Acs3gp3r1OTU27Xet2HIXey7sU3FXsu1r6QkykHy9snmAjjFVvwoAdzUSik8NgPYQCbW1+tzj6zpXn1OUq2gEFNKndVow4QDcLYtsKuiqWSz4SYxl63tTNiSQcmQL9yvLCzybY6Y5MJlXjCV24= x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(1800799024)(366016)(376014)(7416014)(38070700018);DIR:OUT;SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: Ux8PwsKhNQBdybb2XiSpszwOPVf/A1VFha3Sv+9VeuHsx7S59DxX9DXYufDZyNlWJ3X1nitHjAgc/J7Ff3Wiz2eKkgqtVWAMq7XbbRsK8e1UkUjTP0jtOR1JXkAnMU8h+gx82zWL4BKoLw/47xpiWaY3/FG9FoO52nynyqw92YsdekBba4d7vDA5iBI1hXVsyZ+OOZ6HO4TD8280t0SRYtKVOULwZgNGdHM2TKse92a7vInkCzsPf/Hfw4oenySxjyT34suzMcrS87Za3ir51PP1RfJcJJ0rjiV7bpVCUsIHHchntIqy8r1aGld1ZY9LMiNjOF6VcVITIGAhetOYEY2VAxun5x4EjJoe8nujhFe7iinhdXtBm70LDjMhFB3TnUQiklp2fb6rDGfjRrffzUwj4MzAb4P713jdWUHkwYdQQ6Zze6v1+iFiCN/LnVrEHcIuP7ArfQxcQGICmUaa3w/CDN9DS0iWlxtkFxuIf+57ryouDbGvH3KJn8Ieg0VQWxhApwN73aCTBFdy9p1R/UbngolOVDUyHMxtG4+/N1YDQ/EaMp9CaRRaO7fO+l492UUUsS8Z9H9+HTyZP7boWDCgy6BcumRKbwZ7S42XzDcLftNyg+7sYIMiPievFjqQ+Qlr1kaVT82CvkA9NFGvJtEC/ZZIMg8SDesAkfIvZ3W4doGuVHRvKlEo3Co5RSrtexegBKv+gCOjgtGVLKiPIm05bCHQEU/by5s/GsGFuBn1lvnB4MdunZTIVrWv3WuIge0j+FL/V5Qk7B0MpnKaPlPUDOQ2xNBo7O7rSuTZbXaLSZH+A4XTo2fs10usZ56XhzJSOdLi3DPSaZgGsCbRgux8OLlNQYZrVGH9sxYXP4o6b5ogPGCFUqY8C3pePsqYytAgkqHA6OMGpnfNaj6w2UI7MnOusnHGDKWO9jnGfPhTOpgHgpvg4PzNsdipljNS1hqnd1+SE0cYbzfLYW/axNK/ohPAWeNxaj1II7K9L51sl68cCTkh9QDhzInQHUtjxAOkya0flMtUZYjqENIpcgi/t8mtRQ5lp659FrKnBya4NUX9beBQow/Ir8TuozOlKGuoEHhV7jbYyt8dyrAax2qgG733oZw0l1RpTKlh3hKxvYIMaWPuAM1IsGjfNA1GfdPj9qzsnn3V+LOmHfaz1JBADNhARYIAx7ZolkioEHC9bdq06kiFMA85oPfO0sNceT0J9FtPS+hiN9ILHTehvoLLspEJBvWfp/z7CNYkKaHjb09gFQJQZneWjxtJ6AQSEAnzjwzwzT6nG9rw8740MrXG9VOfE55vXgNWEnnp/iobe4u/N+8Qsc+ivItmJRLJncTrhL4ksQ9lvo24Wvtb1tx4nP6aKTbvtBetfpF6ZnwgZxccog95NFHjdMtwVYgMTODIpqkTTvcUHl01QiugvfZPFL9wpLXXLv/B9P1C1lN2dG11xSmlHTC5mb3uWFgcQOk6ZH/+Y+Mb0SpOO4xTVpIlXB+yfwdNq/f3UhcrvrpnnTzGnaNICzpDMtc/tPHr3FNtGRzslZ9QgInmjxHgHnMnAKSHr3jyI1f7CKb6pef4zHvg5C9BZ8J0+OlnmvzlS0YbumC04rMVJzK490BShLy8L5P3tzClHALkxBLIHpDXGn/cIUly8+02gws31VO7CQmG9utZ+yk8i9DHILvV3g== Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-OriginatorOrg: softing.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-Network-Message-Id: 15995782-8292-4e7d-6e6f-08dcc8e9dc9f X-MS-Exchange-CrossTenant-originalarrivaltime: 30 Aug 2024 11:49:53.3571 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: fe3606fa-d397-4238-9997-68dcd7851f64 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: /gtlp5yFwp+n9up8BuRBA8p/+xA8ovvcKqQ9OmJfC+1Yk0v2ItfZ1LZ91gAVQuLmXcPLKIsMGvrBw3qUENpdvYhB1n/zrlVAuIld6eYmpCA= X-MS-Exchange-Transport-CrossTenantHeadersStamped: FR5P281MB4123 From b2e04ce5500faf274654be5284be9db4f3abefce Mon Sep 17 00:00:00 2001 From: Tobias Sperling Date: Fri, 23 Aug 2024 12:08:33 +0200 Subject: [PATCH 1/2] dt-bindings: hwmon: Introduce ADS71x8 Add documentation for the driver of ADS7128 and ADS7138 12-bit, 8-channel analog-to-digital converters. These ADCs have a wide operating range and a wide feature set. Communication is based on an I2C interface. The driver provides the functionality of manually reading single channels or sequentially reading all channels automatically. Signed-off-by: Tobias Sperling --- .../devicetree/bindings/hwmon/ti,ads71x8.yaml | 85 +++++++++++ Documentation/hwmon/ads71x8.rst | 140 ++++++++++++++++++ Documentation/hwmon/index.rst | 1 + 3 files changed, 226 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/ti,ads71x8.yaml create mode 100644 Documentation/hwmon/ads71x8.rst diff --git a/Documentation/devicetree/bindings/hwmon/ti,ads71x8.yaml b/Documentation/devicetree/bindings/hwmon/ti,ads71x8.yaml new file mode 100644 index 000000000000..e422c4ebd207 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ads71x8.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/ti,ads71x8.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS7128/ADS7138 Analog to Digital Converter (ADC) + +maintainers: + - None + +description: | + The ADS7128 is 12-Bit, 8-Channel Sampling Analog to Digital Converter (ADC) + with an I2C interface. + + Datasheets: + https://www.ti.com/product/ADS7128 + https://www.ti.com/product/ADS7138 + +properties: + compatible: + enum: + - ti,ads7128 + - ti,ads7138 + + reg: + maxItems: 1 + + avdd-supply: + description: + The regulator used as analog supply voltage as well as reference voltage. + + ti,mode: + $ref: /schemas/types.yaml#/definitions/uint8 + description: | + Operation mode + Mode 0 - Manual mode. A channel is only sampled when the according input + in the sysfs is read. + Mode 1 - Auto mode. All channels are automatically sampled sequentially. + Reading an input returns the last valid sample. In this mode further + features like statistics and interrupts are available. + default: 0 + + ti,interval: + $ref: /schemas/types.yaml#/definitions/uint16 + description: | + Only considered in mode 1! + Interval in microseconds a new sample is triggered. Is set to closest + possible interval, see datasheet. + default: 1 + + interrupts: + description: | + Only considered in mode 1! + Interrupt specifier the device's ALERT pin is connected to. Level must be + IRQ_TYPE_LEVEL_LOW. If not configured the digital window comparator (DWC) + is not available. + maxItems: 1 + +required: + - compatible + - reg + - avdd-supply + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ads7138@10 { + compatible = "ti,ads7138"; + reg = <0x10>; + avdd-supply = <®_stb_3v3>; + ti,mode = /bits/ 8 <1>; + ti,interval = /bits/ 16 <1000>; + interrupt-parent = <&gpio2>; + interrupts = <12 IRQ_TYPE_LEVEL_LOW>; + status = "okay"; + }; + }; diff --git a/Documentation/hwmon/ads71x8.rst b/Documentation/hwmon/ads71x8.rst new file mode 100644 index 000000000000..383669c1f8c5 --- /dev/null +++ b/Documentation/hwmon/ads71x8.rst @@ -0,0 +1,140 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver ads71x8 +===================== + +Supported chips: + + * Texas Instruments ADS7138 + + Prefix: 'ads7128' + + Datasheet: Publicly available at the Texas Instruments website: + http://focus.ti.com/lit/ds/symlink/ads7128.pdf + + * Texas Instruments ADS7138 + + Prefix: 'ads7138' + + Datasheet: Publicly available at the Texas Instruments website: + http://focus.ti.com/lit/ds/symlink/ads7138.pdf + +Author: Tobias Sperling + (based on ads7828 by Steve Hardy) + +Description +----------- + +This driver implements support for the Texas Instruments ADS7128 and ADS7138, +which are 8-channel 12-bit A/D converters. + +The chip requires an external analog supply voltage AVDD which is also used as +reference voltage. If it is missing or too low, the chip won't show up as I2C +device. + +The driver can be run in different modes. In manual mode a new (averaged) sample +is created when the according input is read. + +In auto mode all channels are sampled sequentially automatically. Reading an +input returns the last valid sample. In this mode there are also further +features like statistics and the possibility to trigger an interrupt if a +voltage drops/raises below/above a specific value (DWC - Digital Window +Comparator). +The overall update time (after which all channels are updated) depends on the +number of samples, the update interval and the amount of channels (8). + + update time = samples * update_interval * 8 + +There is no reliable way to identify this chip, so the driver will not scan +some addresses to try to auto-detect it. That means that you will have to +statically declare the device in the device tree. + +sysfs-Interface +--------------- + +The following interfaces are available in all modes. + ++----------------+----+---------------------------------------------+ +| in[0-7]_input | ro | Voltage in mV sampled at channel [0-7] | ++----------------+----+---------------------------------------------+ +| samples | rw | Number of samples used for averaging 1-128. | +| | | Automatically set to closest power of 2. | ++----------------+----+---------------------------------------------+ +| calibrate | rw | Write any value greater than 0 to trigger | +| | | self-calibration. Reads as 0 if finished. | ++----------------+----+---------------------------------------------+ + +If the device is running in auto mode there are also the following interfaces. + ++------------------+----+-----------------------------------------------------+ +| in[0-7]_max | ro | Maximum value in mV that occurred at channel [0-7] | ++------------------+----+-----------------------------------------------------+ +| in[0-7]_min | ro | Minimal value in mV that occurred at channel [0-7] | ++------------------+----+-----------------------------------------------------+ +| update_interval | ro | Time in microseconds after which the next sample is | +| | | executed. | ++------------------+----+-----------------------------------------------------+ + +If the device is running in auto mode and the interrupt is configured also the +following interfaces are added. If CONFIG_SYSFS is set in the kernel +configuration it is also possible to poll the 'alrarms', see example below. + ++--------------------+----+---------------------------------------------------+ +| alarms | ro | | Contains the flags of DWC events. Once read it | +| | | is reset to 0. | +| | | | BIT0 equals the low event flag of channel 0. | +| | | | BIT7 equals the low event flag of channel 7. | +| | | | BIT8 equals the high event flag of channel 0. | +| | | | BIT15 equals the high event flag of channel 7. | ++--------------------+----+---------------------------------------------------+ +| in[0-7]_max_alarm | rw | Set high threshold in mV of DWC for channel [0-7] | ++--------------------+----+---------------------------------------------------+ +| in[0-7]_min_alarm | rw | Set low threshold in mV of DWC for channel [0-7] | ++--------------------+----+---------------------------------------------------+ + +Example +------- + +.. code:: c + + #include + #include + #include + #include + #include + + int main(void) + { + int retval, fd; + fd_set exceptfds; + char buf[16]; + + fd = open("/sys/class/hwmon/hwmon1/alarms", O_RDONLY); + + while (1) { + + FD_ZERO(&exceptfds); + FD_SET(fd, &exceptfds); + + /* Must be assigned to 'exceptional conditions'. For poll() use + POLLPRI. */ + retval = select(fd + 1, NULL, NULL, &exceptfds, NULL); + if (retval == -1) + perror("select()"); + else if (retval) { + /* Close and reopen is required, since it's a sysfs file */ + close(fd); + fd = open("/sys/class/hwmon/hwmon1/alarms", O_RDONLY); + retval = read(fd, buf, sizeof(buf)); + printf("Received: %.*s\n", retval,buf); + } + } + + close(fd); + exit(EXIT_SUCCESS); + } + +Notes +----- + +TODO support for GPIOs, ADC hysteresis and counts is missing yet. diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 913c11390a45..a54df7af27ea 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -33,6 +33,7 @@ Hardware Monitoring Kernel Drivers adm1275 adm9240 adp1050 + ads71x8 ads7828 adt7410 adt7411 From patchwork Fri Aug 30 11:49:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Sperling, Tobias" X-Patchwork-Id: 13785022 Received: from FR6P281CU001.outbound.protection.outlook.com (mail-germanywestcentralazon11020111.outbound.protection.outlook.com [52.101.171.111]) (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 8F5C514EC41; Fri, 30 Aug 2024 11:50:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.171.111 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725018604; cv=fail; b=bioCEDycWD7FHHMJ/cmrEgw/c48Z2oyi9atR6hPZINWNqAofAFXt9QxtmSYOC//HzMExPNwWrDsMuyqbK8+Q1upDh7t2szY7PxkZgiautZWcsgImKCQhe7+Ly+sNdz/svCJ9ENvVuFJdpQV9Asa7GE8u8r4hAubni/e3Gu0CKPI= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725018604; c=relaxed/simple; bh=8CWaak/pZCZO8z/lJVPu7LDSAb5BJLLpFGFwofU91bg=; h=From:To:CC:Subject:Date:Message-ID:Content-Type:MIME-Version; b=iqwnMIKOKEv1bCkuygE6LIhhgAW8cCKf7AV8d4N+Wo3kmV1Teqku7hksYM3EVLRt8amTRnk6anlqyBG1Xy8MCGx4l635VDnPN3Jfffz293ZfKkNXH+qPmin7zk6emJa8Hci0pdnaq7u2nYAT7JbeBhTFDs4WkHN9Bwm++X5fUUY= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=Softing.com; spf=pass smtp.mailfrom=Softing.com; dkim=pass (2048-bit key) header.d=softing.com header.i=@softing.com header.b=41r0yaKk; arc=fail smtp.client-ip=52.101.171.111 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=Softing.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=Softing.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=softing.com header.i=@softing.com header.b="41r0yaKk" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=tfq3svmdZqd3Dn3vNB9h4HBi3/oiPOZ1vCHuEnPRiWGwC3MImZHMM19/Os4k643I8RqdqFJJkumxYvbgaSkSPZlQTGdaIhLl9W2kILQ32Mmrj1mm1QdUCfd0Ww/+TiuWxt/y9vsmgzGFqu7ogs396Itb6+aY/wWCtGc6AnJUYIYlGva/eFUtrUcpBwzPn16IoFiMFw1AxGno4Z6yVi0ArNYwz1mjYZIYvF6McFWAvxykF39BKocJ7FrgADgyGRvGlUM3J4x6T1ooDJ4shB74VHv7hemJJx2k43srgx2FtyYxu1rgHVfu1U2Q1Mb4OxXGyBBQTuP7IKZ4n4/dFRPQzw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=LHpRLIz3n7m0ZZ7e26E+e30NvMPKdoLbhemD3VX3nMc=; b=MXu02LERkenwVHD/mtA1QD1jJ95ng7YjUuc6ft41GOFYRjKblR1FkynQ0i715eD6CvGbQfGoGO16HFmjC3kBMh6UZHFifChDlmRhQ/My3l9cjX6A4lElAx0tfbWOuR3n3Qzi1PF1ODLv6xP8kyKrF2feYbktVT6bX4tVikR+v40jylk1VDCTUeEzBDErHo2za1eXDWWnQVkprCZ7iAbCUNhJXzYaQgjBKNi0Cm7npPkfoTC3Cnc3XTvPiJbFTiOW8C6wewEWDUp3hC7wSAGBtcD3cswqdOHC/m7Z9ye+HTCdtp06Io7FsAm8MOncZbZm5WY/vqrNMITpni38asA3xQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=softing.com; dmarc=pass action=none header.from=softing.com; dkim=pass header.d=softing.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=softing.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=LHpRLIz3n7m0ZZ7e26E+e30NvMPKdoLbhemD3VX3nMc=; b=41r0yaKkwHuI3NUh8DHkQdr6UR0jZCsJCHgwlJCdXVps2qQFH/9xZ0GBdhCzjIFj88erYiPRo+q7fM+mN0JRphu8JrVOk0fcUoWZugCUlFmYB/nzEKjoBlR5d51f52Fgzo8tJPvOjaDXtWXcSx9qM5/cmlETSZNgWyUf/MPSkWbteQe45RWTxqd5YPABSPIIdRJ0L+GndBFugMnkvGflcn1RZYUwOuDlVBaR8Gqdd8q5wzmpAlxJnQt0wZZQcSQCHFyOhls2o4SY6FPA9a9QjMvI/FAk9ItCcPpGAfBw0Xs52qCe22nrtEOmk7EH6eLb8Qoi6DxSWW0OkbR7S4q5Fw== Received: from BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM (2603:10a6:b10:43::7) by FR2P281MB2926.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:65::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7918.20; Fri, 30 Aug 2024 11:49:57 +0000 Received: from BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM ([fe80::8de2:b2ba:4092:939a]) by BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM ([fe80::8de2:b2ba:4092:939a%6]) with mapi id 15.20.7918.019; Fri, 30 Aug 2024 11:49:56 +0000 From: "Sperling, Tobias" To: "linux-kernel@vger.kernel.org" , "linux-hwmon@vger.kernel.org" CC: "jdelvare@suse.com" , "linux@roeck-us.net" , "Sperling, Tobias" Subject: [PATCH 2/2] hwmon: Add driver for ADS71x8 Thread-Topic: [PATCH 2/2] hwmon: Add driver for ADS71x8 Thread-Index: Adr60nxZNNNsP9NXTlO1squBQ91rSA== Date: Fri, 30 Aug 2024 11:49:56 +0000 Message-ID: Accept-Language: de-DE, en-US Content-Language: de-DE X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=Softing.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: BE1P281MB2420:EE_|FR2P281MB2926:EE_ x-ms-office365-filtering-correlation-id: 617b6842-c63d-461c-e18c-08dcc8e9deb7 x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|1800799024|366016|376014|38070700018; x-microsoft-antispam-message-info: 6TyNNzrxjQR0p0JnlcMXGqmNKZmYqyzvjW8hbk4khjz8zu6wLqbshvvXnM3LKrO70Gk6pivQ1vF/5Bn47+s1HBOvtuOIqgglxiqhzpAv+HtbrO0ZolRYKy/B1Gkc3DLYLJGo/AYZrfg85j/Hw8YRz5qn5Rdk2Z56oPZgy5eoLdSoRDYreunjpuAY/ldTrkuN6/hHA55ZV/1UV6sNIx91567pyPOMY4LRmunCGxdm0PQRnrdz6INKLjnkzqhhyccnQP9O47IowlK+KqxW5BQWgALVeFaJU44rYQpSnVbpJ7Frl3ErieOUyRUVjrTjnuNQzbYPyGIOkcDtFw/ZFMmCNLu2L5vGT3c59PkAQUaC/6C6bcIhnYDOLlDQ3ejXcaUN5dTevpAiW3wGS56vCWH4eMzV/hv5hjoIawQJx7MI+4KG10PSqmBLJlQM7sdk+0TkIFDZW68na7YkbaB6f3FZTaC3S3pPjhZsGoPzyS2qBsRWyUGyVsGUWPA2aq9pXW6IahDbXiezR8KukWIQ5ojIp2Ld9AmEx2zRk2e4KScsTSVmSh34jleTYJBF57vMGh/UPleO2P70sYJ+fGP+pI3MtEKK69X2c7g+er+AH894OjOS8HAx049To5TnA8oW/NF2Me0xQNIw2gTBZQ7/vqFmpGhvUbp/Xp9fg6O7JaixFWMfs3ZwsDkvICq6O0CuwrU7lCxSFymYguctiRWXbvGpQ5UaudmDLIcEM52p0mm4RTmw3+KCjSo2DWgYXYeTmKX/C3pKxng5t+9WzDCHBzoMLZNKj0UZj3r0e5e8I5Y4ZLlYtWmVpr8LCwgjzCQzoBFYwzB1XENafIB5k6IrVCEn8VdMr96l/llAQ9vVE3o7qrUGH395ffIBezQQRvQrIHKd/WcGo4SdmC+VNeppwCHYiIQOvK9I+82Nu2qM6xozoYPfv2uz4cFRwXIw+J8j+PVM4rtVE535/1er/lnm/31p58yFp0bOGq1bbMlWQKEnVh6R9NhuY7KrBD6p8yjtXu1Js30P8JMIkyQlsnftYWxofrh1tC+orp9lZarQS5XGmPVId6SAXGPgDSqEKdnGAV/VKtd2TqKho2qIMTHEIMGpccVK31H8XpgERe9j+hPxwHi5ZD9t6wUrhT1ZnxHD+Tf5I2XEaGJ6BLEEsHcSGNlPLsnFS03aeeq/tvaHNJT4T7R05nCtqXUnXh21xwV6GC8TmdCGG87+V6QNp+BgDeyjAYY9xWMU4Op2S35M0DH+aZy4+KYdEkbVYNWVdPXAgJP5Fom6moGek32afAyH7owMnh4VZw3qD5hsGlW/8g8uYOesjKshnwVFpv9OrY1HM9WkqAKzKyCA9QEd9A8h6VJZdIe9h6yA8x0ezpM2xRWZgRIfE8NkNO/HR/6xVQo5SYF+Xi1b+QgBw0DgDNKWFPsWqA== x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(1800799024)(366016)(376014)(38070700018);DIR:OUT;SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: xpdAngn+qjpm5VGCd4HVitA5GiQ1W9G2iFk/FY8JiWeSPj12XJ0UeOsXRxqKWzewmT9zEUgViRdGcb0S0hT/s5Q3XHY1Qbd1Q7M7Wuy3ivL+WsBKuQstVlJNwlFWqg6Ll0Yryr3ffRKjQTNvx9fW50omCpgN+vofxJwgXl4f2jtrUPv1PE0yJi1tZrLE7uA75u2/8PLpmHKDh5MHP+VOehKGaYNeM4ElJ0PtE5MI2kR1kW1mz12AJgGKdOPetxZX6fIMnk28vALzMETrA9lGNg6A+P0Idj/VIbhMl9Mmf7HGV6e2nrdkUj6cILfhD9t4g4xqf6+h/nyKYC6/bMovk5hhO2zt6H84lHSB62i8C96qvYqdn2lvLjlOPvXjU+brl1YCngU0dBfX6ahVFLl4zqGDTP+P1aItGEZXVvgN6jHnzUr+hhO8LL819jUji9hrQPF4bIkU6HiPj0KjqLI0l+HJRjqVpQ4TGvdeYvjH1M8gVqGNB0yFPHI76SeYZrVMV7JY7txCqQs1ehT2QK7TGOpNYxsU+pUl/R4pgcBDZBQcC/YvXdi1A7ArqGO4AED6VdwCJLsX/Y5IhRYPJurVtRb1q4ePXFhNHHDBT4aZ//ji07puRuyWykDXscDGlV8L7Mv1FMp8WwKhK57AgLWKbl587X9xV0cxdiapYaOWSK/dvdIUkqVHnMOgCNz1kq22kt1WWJ51QNqoR5OrYIRd8mlLeVaTYnJpw/+5MswTweW+25E+hua12jRS60nl0wH4Z3DGxmfCVdznXuAMBbKOzgWBnTBz+jbnOuJKjqAmqbZhtaE9HeFW0CQNgawOqHgksqvgQ/JtwmFz9GWLlnBuZRp161M1FRGveLlDIdTFRSgTKDprpY14F6iRFkhK/nRKqWCHRzEpQtyOl0VlO8Ag+8LUd0nFjiXaDrONuZl8V7JgoMw1b5wUm6SV5c4YL3VxmmUU/9aNHgFndU3JjgIjogY+iP4VlpXHV1/BWKQcORqYd6SZ03y4xHZGtyK5iIJ9B9tCgBl7D24zuOfOCI/ss0TRQOXZ5zcRb4OJIUukFJvu9oDjiyF8IEZB4DxBn7AIH5bp3kKaknZyJb+fbflO3rjvy/ECUBBznZU3oT/Cw2ejgf4Emkcy3tDXuGfOfy+Vmd7To2rcqI0lckb6y+ppbey2Mj7Xau5gB5/HxVera+wbUsahokn5lPM3RR/O0nPvzbILjqDbuhD9xqKYDr4X9FfmazinQUJcR5PCn6UJQPxasQCy9DV8+zvAXF0ofqM0NJkzebFhXtME+S/vFATVSi7KYjTQgC+lGFRI4+ISVHvBHg6WQ6Facv665e0wINmqjq/etdCROHQqvZcYhsERCYhMMr1VDgmbf6kXQLVnsDO2n5rRqcQ5F90BfGoJ4e1vkpZYsjUKl3esYPWubIWXVC6aaS4Vp4e5zSFXobqOGzLGkcpy0HqaSglhW2ypee5gMZYeYVgUQwFVQpsVnSiXvhNps5ZgojG6EAkGUZtxlhNb9/DoVSvP1P/yIMJsQF5IyWp+p5HobDBOG5f6Czu8/IDf5rAlFzPXhM2mhBhYaFSo1XF0CvAOxXPA9ICSDYG/T6HqSNuaiJj68HO2HAn1Z0YwQPFodAwA2bRfM71EQt3MOGtMR/7W2gIDonVDXdzpAHfHZlfTXLRRkFsX8e1YQQ== Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-OriginatorOrg: softing.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-Network-Message-Id: 617b6842-c63d-461c-e18c-08dcc8e9deb7 X-MS-Exchange-CrossTenant-originalarrivaltime: 30 Aug 2024 11:49:56.9208 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: fe3606fa-d397-4238-9997-68dcd7851f64 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: BkxrmM9Nv/b/+h78zQCW3DT8Q2ig6lya8lIdCAF6Gw12pMvmc520o2MxK+js9lfT6PS937/reOJrrE+2ZiFKdjd29ARr461tJ7cSeD1L9g8= X-MS-Exchange-Transport-CrossTenantHeadersStamped: FR2P281MB2926 From 4b2836f3984ceee899b55448fc4b25cf27e5912e Mon Sep 17 00:00:00 2001 From: Tobias Sperling Date: Fri, 23 Aug 2024 12:07:50 +0200 Subject: [PATCH 2/2] hwmon: Add driver for ADS71x8 Add driver for ADS7128 and ADS7138 12-bit, 8-channel analog-to-digital converters. These ADCs have a wide operating range and a wide feature set. Communication is based on an I2C interface. The driver provides the functionality of manually reading single channels or sequentially reading all channels automatically. Signed-off-by: Tobias Sperling --- drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ads71x8.c | 702 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 713 insertions(+) create mode 100644 drivers/hwmon/ads71x8.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b60fe2e58ad6..062ff1dfc8fa 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2090,6 +2090,16 @@ config SENSORS_ADC128D818 This driver can also be built as a module. If so, the module will be called adc128d818. +config SENSORS_ADS71X8 + tristate "Texas Instruments ADS7128 and ADS7138" + depends on I2C + help + If you say yes here you get support for Texas Instruments ADS7128 and + ADS7138 8-channel A/D converters with 12-bit resolution. + + This driver can also be built as a module. If so, the module + will be called ads71x8. + config SENSORS_ADS7828 tristate "Texas Instruments ADS7828 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b1c7056c37db..e6488368b890 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM1177) += adm1177.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o +obj-$(CONFIG_SENSORS_ADS71X8) += ads71x8.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o obj-$(CONFIG_SENSORS_ADT7X10) += adt7x10.o diff --git a/drivers/hwmon/ads71x8.c b/drivers/hwmon/ads71x8.c new file mode 100644 index 000000000000..f9334ba7187c --- /dev/null +++ b/drivers/hwmon/ads71x8.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ads71x8.c - driver for TI ADS71x8 8-channel A/D converter and compatibles + * + * For further information, see the Documentation/hwmon/ads71x8.rst file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "ads71x8" + +/* AVDD (VREF) operating range */ +#define ADS71x8_AVDD_MV_MIN 2350 +#define ADS71x8_AVDD_MV_MAX 5500 + +/* ADS71x8 operation codes */ +#define ADS71x8_OPCODE_WRITE 0x08 +#define ADS71x8_OPCODE_SET_BIT 0x18 +#define ADS71x8_OPCODE_BLOCK_WRITE 0x28 +#define ADS71x8_OPCODE_BLOCK_READ 0x30 + +/* ADS71x8 registers */ +#define ADS71x8_REG_GENERAL_CFG 0x01 +#define ADS71x8_REG_OSR_CFG 0x03 +#define ADS71x8_REG_OPMODE_CFG 0x04 +#define ADS71x8_REG_SEQUENCE_CFG 0x10 +#define ADS71x8_REG_CHANNEL_SEL 0x11 +#define ADS71x8_REG_AUTO_SEQ_CH_SEL 0x12 +#define ADS71x8_REG_ALERT_CH_SEL 0x14 +#define ADS71x8_REG_EVENT_FLAG 0x18 +#define ADS71x8_REG_EVENT_HIGH_FLAG 0x1A +#define ADS71x8_REG_EVENT_LOW_FLAG 0x1C +#define ADS71x8_REG_HIGH_TH_CH0 0x21 +#define ADS71x8_REG_LOW_TH_CH0 0x23 +#define ADS71x8_REG_MAX_CH0_LSB 0x60 +#define ADS71x8_REG_MIN_CH0_LSB 0x80 +#define ADS71x8_REG_RECENT_CH0_LSB 0xA0 + +/* + * Modes after ADS71x8_MODE_MAX can't be selected by configuration + * and are only intended for internal use of the driver. + */ +enum ads71x8_modes { ADS71x8_MODE_MANUAL, ADS71x8_MODE_AUTO, + ADS71x8_MODE_MAX, ADS71x8_MODE_AUTO_IRQ }; + +/* Client specific data */ +struct ads71x8_data { + const struct i2c_device_id *id; + struct i2c_client *client; + struct device *hwmon_dev; + int vref; /* Reference voltage in mV */ + struct mutex lock; + u8 mode; + u16 interval_us; /* Interval in us a new conversion is triggered */ + long alarms; /* State of window comparator events */ +}; + +struct ads71x8_val_map { + u16 val; + u8 bits; +}; + +static const struct ads71x8_val_map ads71x8_intervals_us[] = { + { 1, 0x0 }, { 2, 0x02 }, { 3, 0x03 }, { 4, 0x04 }, { 6, 0x05 }, + { 8, 0x06 }, { 12, 0x07 }, { 16, 0x08 }, { 24, 0x09 }, { 32, 0x10 }, + { 48, 0x11 }, { 64, 0x12 }, { 96, 0x13 }, { 128, 0x14 }, + { 192, 0x15 }, { 256, 0x16 }, { 384, 0x17 }, { 512, 0x18 }, + { 768, 0x19 }, { 1024, 0x1A }, { 1536, 0x1B }, { 2048, 0x1C }, + { 3072, 0x1D }, { 4096, 0x1E }, { 6144, 0x1F } +}; + +/* List of supported devices */ +enum ads71x8_chips { ads7128, ads7138 }; + +static const struct i2c_device_id ads71x8_device_ids[] = { + { "ads7128", ads7128 }, + { "ads7138", ads7138 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads71x8_device_ids); + +static const struct of_device_id __maybe_unused ads71x8_of_match[] = { + { + .compatible = "ti,ads7128", + .data = (void *)ads7128 + }, + { + .compatible = "ti,ads7138", + .data = (void *)ads7138 + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ads71x8_of_match); + +static int ads71x8_i2c_write_block(const struct i2c_client *client, u8 reg, + u8 *values, u8 length) +{ + struct ads71x8_data *data = i2c_get_clientdata(client); + int ret; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = length + 2, /* "+ 2" for OPCODE and reg */ + }, + }; + + msgs[0].buf = kmalloc(msgs[0].len, GFP_KERNEL); + if (!msgs[0].buf) + return -ENOMEM; + + msgs[0].buf[0] = ADS71x8_OPCODE_BLOCK_WRITE; + msgs[0].buf[1] = reg; + memcpy(&msgs[0].buf[2], values, length); + + mutex_lock(&data->lock); + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + mutex_unlock(&data->lock); + kfree(msgs[0].buf); + + return ret; +} + +static int ads71x8_i2c_write(const struct i2c_client *client, u8 reg, u8 value) +{ + return ads71x8_i2c_write_block(client, reg, &value, sizeof(value)); +} + +static int ads71x8_i2c_set_bit(const struct i2c_client *client, u8 reg, u8 bits) +{ + struct ads71x8_data *data = i2c_get_clientdata(client); + int ret; + u8 buf[3] = {ADS71x8_OPCODE_SET_BIT, reg, bits}; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = ARRAY_SIZE(buf), + .buf = buf, + }, + }; + + mutex_lock(&data->lock); + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + mutex_unlock(&data->lock); + + return ret; +} + +static int ads71x8_i2c_read_block(const struct i2c_client *client, u8 reg, + u8 *out_values, u8 length) +{ + struct ads71x8_data *data = i2c_get_clientdata(client); + int ret; + u8 buf[2] = {ADS71x8_OPCODE_BLOCK_READ, reg}; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = ARRAY_SIZE(buf), + .buf = buf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = out_values, + }, + }; + + mutex_lock(&data->lock); + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + mutex_unlock(&data->lock); + + return ret; +} + +static int ads71x8_i2c_read(const struct i2c_client *client, u8 reg) +{ + u8 value; + int ret = ads71x8_i2c_read_block(client, reg, &value, sizeof(value)); + + if (ret < 0) + return ret; + return value; +} + +static int ads71x8_i2c_read_manual(const struct i2c_client *client, u8 channel, + u16 *out_value) +{ + struct ads71x8_data *data = i2c_get_clientdata(client); + int ret; + u8 buf[3] = {ADS71x8_OPCODE_WRITE, ADS71x8_REG_CHANNEL_SEL, channel}; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = ARRAY_SIZE(buf), + .buf = buf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 2, + .buf = buf, + }, + }; + + mutex_lock(&data->lock); + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + mutex_unlock(&data->lock); + + /* + * For manual reading the order of LSB and MSB is swapped in comparison + * to the other registers. + */ + *out_value = ((buf[0] << 8) | buf[1]); + + return ret; +} + +static int ads71x8_read_input_mv(struct ads71x8_data *data, u8 reg, long *val) +{ + u8 values[2]; + int ret; + + ret = ads71x8_i2c_read_block(data->client, reg, values, + ARRAY_SIZE(values)); + if (ret < 0) + return ret; + + /* + * Mask the lowest 4 bits, because it has to be masked for some + * registers and doesn't change anything in the result, anyway. + */ + *val = ((values[1] << 8) | (values[0] & 0xF0)); + /* + * Standard resolution is 12 bit, but can get 16 bit if oversampling is + * enabled. Therefore, use 16 bit all the time, because the registers + * are aligned like that anyway. + */ + *val = DIV_ROUND_CLOSEST(*val * data->vref, (1 << 16)); + return 0; +} + +static int ads71x8_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct ads71x8_data *data = dev_get_drvdata(dev); + u8 reg, values[2]; + u16 tmp_val; + int ret; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + ret = ads71x8_i2c_read(data->client, ADS71x8_REG_OSR_CFG); + if (ret < 0) + return ret; + *val = (1 << (ret & 0x07)); + return 0; + case hwmon_chip_update_interval: + *val = data->interval_us; + return 0; + case hwmon_chip_alarms: + *val = data->alarms; + /* Reset alarms after reading */ + data->alarms = 0; + return 0; + default: + return -EOPNOTSUPP; + } + + case hwmon_in: + switch (attr) { + case hwmon_in_input: + if (data->mode == ADS71x8_MODE_MANUAL) { + ret = ads71x8_i2c_read_manual(data->client, + channel, &tmp_val); + *val = tmp_val; + } else { + reg = ADS71x8_REG_RECENT_CH0_LSB + (2 * channel); + ret = ads71x8_i2c_read_block(data->client, reg, + values, ARRAY_SIZE(values)); + *val = ((values[1] << 8) | values[0]); + } + if (ret < 0) + return ret; + *val = DIV_ROUND_CLOSEST(*val * data->vref, (1 << 16)); + return 0; + case hwmon_in_min: + reg = ADS71x8_REG_MIN_CH0_LSB + (2 * channel); + return ads71x8_read_input_mv(data, reg, val); + case hwmon_in_max: + reg = ADS71x8_REG_MAX_CH0_LSB + (2 * channel); + return ads71x8_read_input_mv(data, reg, val); + case hwmon_in_min_alarm: + reg = ADS71x8_REG_LOW_TH_CH0 - 1 + (4 * channel); + return ads71x8_read_input_mv(data, reg, val); + case hwmon_in_max_alarm: + reg = ADS71x8_REG_HIGH_TH_CH0 - 1 + (4 * channel); + return ads71x8_read_input_mv(data, reg, val); + default: + return -EOPNOTSUPP; + } + + default: + return -EOPNOTSUPP; + } +} + +static u32 get_closest_log2(u32 val) +{ + u32 down = ilog2(val); + u32 up = ilog2(roundup_pow_of_two(val)); + + return (val - (1 << down) < (1 << up) - val) ? down : up; +} + +static int ads71x8_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct ads71x8_data *data = dev_get_drvdata(dev); + u8 reg, values[2]; + int ret; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + /* Number of samples can only be a power of 2 */ + values[0] = get_closest_log2(clamp_val(val, 1, 128)); + ret = ads71x8_i2c_write(data->client, + ADS71x8_REG_OSR_CFG, values[0]); + return ret < 0 ? ret : 0; + default: + return -EOPNOTSUPP; + } + + case hwmon_in: + switch (attr) { + case hwmon_in_min_alarm: + reg = ADS71x8_REG_LOW_TH_CH0 - 1 + (4 * channel); + val = DIV_ROUND_CLOSEST(val * (1 << 16), data->vref); + val = clamp_val(val, 0, 65535); + values[0] = (val & 0xF0); + values[1] = (val >> 8) & 0xFF; + ret = ads71x8_i2c_write_block(data->client, reg, + values, ARRAY_SIZE(values)); + return ret < 0 ? ret : 0; + case hwmon_in_max_alarm: + reg = ADS71x8_REG_HIGH_TH_CH0 - 1 + (4 * channel); + val = DIV_ROUND_CLOSEST(val * (1 << 16), data->vref); + val = clamp_val(val, 0, 65535); + values[0] = (val & 0xF0); + values[1] = (val >> 8) & 0xFF; + ret = ads71x8_i2c_write_block(data->client, reg, + values, ARRAY_SIZE(values)); + return ret < 0 ? ret : 0; + default: + return -EOPNOTSUPP; + } + + default: + return -EOPNOTSUPP; + } +} + +static umode_t ads71x8_is_visible(const void *_data, + enum hwmon_sensor_types type, u32 attr, int channel) +{ + u8 mode = ((struct ads71x8_data *)_data)->mode; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_samples: + return 0644; + case hwmon_chip_update_interval: + return mode >= ADS71x8_MODE_AUTO ? 0444 : 0; + case hwmon_chip_alarms: + return mode >= ADS71x8_MODE_AUTO_IRQ ? 0444 : 0; + default: + return 0; + } + + case hwmon_in: + switch (attr) { + case hwmon_in_input: + return 0444; + case hwmon_in_min: + case hwmon_in_max: + return mode >= ADS71x8_MODE_AUTO ? 0444 : 0; + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return mode >= ADS71x8_MODE_AUTO_IRQ ? 0644 : 0; + default: + return 0; + } + + default: + return 0; + } +} + +static const struct hwmon_channel_info *ads71x8_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_SAMPLES | HWMON_C_ALARMS | HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM), + NULL +}; + +static const struct hwmon_ops ads71x8_hwmon_ops = { + .is_visible = ads71x8_is_visible, + .read = ads71x8_read, + .write = ads71x8_write, +}; + +static const struct hwmon_chip_info ads71x8_chip_info = { + .ops = &ads71x8_hwmon_ops, + .info = ads71x8_info, +}; + +static ssize_t ads71x8_cal_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ads71x8_data *data = dev_get_drvdata(dev); + int ret; + + ret = ads71x8_i2c_read(data->client, ADS71x8_REG_GENERAL_CFG); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", (ret & 0x02)); +} + +static ssize_t ads71x8_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ads71x8_data *data = dev_get_drvdata(dev); + int ret; + long val; + + ret = kstrtol(buf, 10, &val); + if (ret < 0) + return ret; + + if (val == 0) + return count; + + ret = ads71x8_i2c_set_bit(data->client, ADS71x8_REG_GENERAL_CFG, 0x02); + if (ret < 0) + return ret; + + return count; +} + +static SENSOR_DEVICE_ATTR_RW(calibrate, ads71x8_cal, 0); + +static struct attribute *ads71x8_attrs[] = { + &sensor_dev_attr_calibrate.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(ads71x8); + +static const struct ads71x8_val_map *get_closest_interval(u16 freq) +{ + const int idx_max = ARRAY_SIZE(ads71x8_intervals_us) - 1; + u16 cur, best = ads71x8_intervals_us[idx_max].val; + int i; + + freq = clamp_val(freq, ads71x8_intervals_us[0].val, + ads71x8_intervals_us[idx_max].val); + + for (i = 0; i <= idx_max; i++) { + cur = abs(ads71x8_intervals_us[i].val - freq); + if (cur > best) + return &ads71x8_intervals_us[i-1]; + best = cur; + } + return &ads71x8_intervals_us[0]; +} + +static irqreturn_t ads71x8_irq_handler(int irq, void *_data) +{ + struct ads71x8_data *data = _data; + struct device *dev = &data->client->dev; + int ret; + + ret = ads71x8_i2c_read(data->client, ADS71x8_REG_EVENT_FLAG); + if (ret <= 0) + return IRQ_NONE; + + ret = ads71x8_i2c_read(data->client, ADS71x8_REG_EVENT_HIGH_FLAG); + if (ret < 0) + goto out; + data->alarms |= (ret << 8); + + ret = ads71x8_i2c_read(data->client, ADS71x8_REG_EVENT_LOW_FLAG); + if (ret < 0) + goto out; + data->alarms |= (ret); + + /* Clear all interrupt flags, so next interrupt can be captured */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_EVENT_HIGH_FLAG, 0xFF); + if (ret < 0) + goto out; + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_EVENT_LOW_FLAG, 0xFF); + if (ret < 0) + goto out; + + /* Notify poll/select in userspace. CONFIG_SYSFS must be set! */ + sysfs_notify(&data->hwmon_dev->kobj, NULL, "alarms"); + kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE); + +out: + if (ret < 0) + dev_warn(dev, "couldn't handle interrupt correctly: %d\n", ret); + return IRQ_HANDLED; +} + +static int ads71x8_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ads71x8_data *data; + struct device *hwmon_dev; + struct regulator *regulator; + const struct ads71x8_val_map *interval = ads71x8_intervals_us; + int vref, ret, err = 0; + + data = devm_kzalloc(dev, sizeof(struct ads71x8_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto out; + } + + data->client = client; + data->id = i2c_match_id(ads71x8_device_ids, client); + i2c_set_clientdata(client, data); + mutex_init(&data->lock); + + /* Reset the chip to get a defined starting configuration */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_GENERAL_CFG, 0x01); + if (ret < 0) { + dev_err(dev, "failed to reset\n"); + err = ret; + goto cleanup_mutex; + } + + /* Get AVDD (in mv) which is the analog supply and reference voltage */ + regulator = devm_regulator_get(dev, "avdd"); + if (IS_ERR(regulator)) { + err = PTR_ERR(regulator); + goto cleanup_mutex; + } + + vref = regulator_get_voltage(regulator); + data->vref = DIV_ROUND_CLOSEST(vref, 1000); + if (data->vref < ADS71x8_AVDD_MV_MIN || data->vref > ADS71x8_AVDD_MV_MAX) { + dev_err(dev, "invalid value for AVDD %d\n", data->vref); + err = -EINVAL; + goto cleanup_mutex; + } + + /* + * Try reading optional parameter 'ti,mode', otherwise keep current + * mode, which is manual mode. + */ + if (of_property_read_u8(dev->of_node, "ti,mode", &data->mode) == 0) { + if (data->mode >= ADS71x8_MODE_MAX) { + dev_err(dev, "invalid operation mode %d\n", data->mode); + err = -EINVAL; + goto cleanup_mutex; + } + } + + if (data->mode <= ADS71x8_MODE_MANUAL) + goto conf_manual; + + /* Try reading optional parameter 'ti,interval' */ + if (of_property_read_u16(dev->of_node, "ti,interval", &data->interval_us) == 0) + interval = get_closest_interval(data->interval_us); + data->interval_us = interval->val; + + /* Check if interrupt is also configured */ + if (!client->irq) { + dev_warn(dev, "interrupt not available, intended?\n"); + goto conf_auto; + } + + data->mode = ADS71x8_MODE_AUTO_IRQ; + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, ads71x8_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, + NULL, data); + if (ret) { + dev_err(dev, "unable to request IRQ %d\n", client->irq); + err = ret; + goto cleanup_mutex; + } + + /* Enable possibility to trigger an alert/interrupt for all channels */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_ALERT_CH_SEL, 0xFF); + if (ret < 0) { + err = ret; + goto cleanup_config; + } + +conf_auto: + /* Set to autonomous conversion and update interval */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_OPMODE_CFG, + 0b00100000 | (interval->bits & 0x1F)); + if (ret < 0) { + err = ret; + goto cleanup_config; + } + + /* Enable statistics and digital window comparator */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_GENERAL_CFG, + 0b00110000); + if (ret < 0) { + err = ret; + goto cleanup_config; + } + + /* Enable all channels for auto sequencing */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_AUTO_SEQ_CH_SEL, + 0xFF); + if (ret < 0) { + err = ret; + goto cleanup_config; + } + + /* Set auto sequence mode and start sequencing */ + ret = ads71x8_i2c_write(data->client, ADS71x8_REG_SEQUENCE_CFG, + 0b00010001); + if (ret < 0) { + err = ret; + goto cleanup_config; + } + +conf_manual: + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &ads71x8_chip_info, ads71x8_groups); + if (IS_ERR_OR_NULL(hwmon_dev)) { + err = PTR_ERR_OR_ZERO(hwmon_dev); + goto cleanup_mutex; + } + data->hwmon_dev = hwmon_dev; + + goto out; + +cleanup_config: + dev_err(dev, "failed to configure IC: %d\n", err); +cleanup_mutex: + mutex_destroy(&data->lock); +out: + return err; +} + +static void ads71x8_remove(struct i2c_client *client) +{ + struct ads71x8_data *data = i2c_get_clientdata(client); + + /* Reset the chip */ + if (ads71x8_i2c_write(data->client, ADS71x8_REG_GENERAL_CFG, 0x01) < 0) + dev_err(&client->dev, "failed to reset\n"); + + mutex_destroy(&data->lock); +} + +static struct i2c_driver ads71x8_driver = { + .driver = { + .name = MODULE_NAME, + .of_match_table = of_match_ptr(ads71x8_of_match), + }, + .id_table = ads71x8_device_ids, + .probe = ads71x8_probe, + .remove = ads71x8_remove, +}; +/* Cares about module_init and _exit */ +module_i2c_driver(ads71x8_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tobias Sperling "); +MODULE_DESCRIPTION("Driver for TI ADS71x8 ADCs");