From nobody Mon Sep 28 16:21:19 2026 Received: from MRWPR03CU001.outbound.protection.outlook.com (mail-francesouthazon11011037.outbound.protection.outlook.com [40.107.130.37]) (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 ECFE83C455B; Sun, 27 Sep 2026 21:29:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.130.37 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790544594; cv=fail; b=Dl7MALiWoEW5v5ZR/zrcNn+93HPDIcb2aCO4zr9DnDTEkLyBionDSUXF8YEaKNoD4hatKgC4c2dzgejZ2NOuP14iSfSOhBPB9fq6SoxmLJ7fmzCZgFtsX1ai7n+s9BJIMG+HzvjUBnEwucsRlk/dNVYYRDn/IDCSgAW+H4SMC0g= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790544594; c=relaxed/simple; bh=q/aGJzwGYsf9RO7TFR7EqcS8hVe0jyzkVFNXQk6PIio=; h=From:To:CC:Subject:Date:Message-ID:References:In-Reply-To: Content-Type:MIME-Version; b=FJ4Z/SRbsvG75+hpkJapm6ovyRFThWs2oDgUUfElh5NahLfelWjYIAbt1Wrwc+I11jS1rQAzvZOl6di8I2UeYBP50N+YE+NZwt6+9QBRvNv50vnt6s+xvWrxlT8VTuBkE0vLmGCEgBpYMy7WWTgX9TJiFXORqC7QmFkeMK/qhjg= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=est.tech; spf=pass smtp.mailfrom=est.tech; dkim=pass (2048-bit key) header.d=est.tech header.i=@est.tech header.b=LR8SbpXt; arc=fail smtp.client-ip=40.107.130.37 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=est.tech Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=est.tech Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=est.tech header.i=@est.tech header.b="LR8SbpXt" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=xwk15VrcHUZOgwia5tlrZ0l0FwmreqEvL1qoP2JoxOey8MmxrVk+No1wqjCfHztMjHjV2Y5LTUy0n3Tf+3fKJl2vmm3bTie1bYJa2Bl8LADoDqmDYLfRQAVYmK9HLLh7nUxbsq+fzxGFOUoTD9Y+itrWG04jlDvfEfJCDW3jkw5zELQBaR+USCrId1Edx5I3mFRIcHUjuFPKBOGtupx1nVUtWF5d/DyAyC9UJZJ5fU5oHW8I150PJnZAGg5exvFushBY91+jkqVjTRIReQRQqoVydPVSKqkcFBtiH7YU0mJ53iG1FCszz+97o+sIOUNEJXsu4bQe+c2VT/7xjWUl0g== 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=qpMy0NiTCFlIoKUdsAhaP5jGJUkWBRUcfevbrp4XzqQ=; b=RuC2X4+dYLNmaMzEXvGpuJSWv2pw7dQGeYlLRg9VHlFtdYsaO1rD/Zn0DVNmvBbRAiriu+duqn0kByOrkMIo7Qkgr1TKT/y4wVoOpw8Q8Agl+eQtgeSJG13IqXYxCQs2EteePQFiGmcDfhPtf+R4AfRJuk15gG6aKt6Yb0wXUpfT9KWiDbDH4kUnaY245VWSSJgGIueZ4JvHIJOtz1c2zVDtz+EWMtJa39S+ltXDv8zjbD12QX5WrzWm8j3xLvRo9DvHHjtaQWumAt3fpa4m6izb/7cS1wRzc5Fkr1Iu7Ch4HGjGLlKrIXlSbUxPWAG4e+qh1CVRpYN0alZ8rJbqSA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=est.tech; dmarc=pass action=none header.from=est.tech; dkim=pass header.d=est.tech; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=est.tech; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=qpMy0NiTCFlIoKUdsAhaP5jGJUkWBRUcfevbrp4XzqQ=; b=LR8SbpXtZamd4sAC1/1Mc4LSVfnsMj3YALeSJdChq5RLFYdERNfbWA/Cd0kNYg2KzKCbKfyj5FxjNPuxNyaYRa269dK/uy504UtoxIpvs1jJr7CySsaQzgCV2jRfjvoLfQr1Xg9puDJ/3usbfzZH7OH9Jvt92nv6mp2hw0xsXch4Djnjho0stDffk1xzPXGY5pzZiNhMM4zwzNaHSDUzjHNLoAJE5h8fAafnAa7yXnfvo6LLYmk8nqj+I9qs1s8UeWlrxe+ERdtyi9lbmieHYbTBwa6RjVM1wHpmXlEyQ7EQmdzefGrIhZlySaPh2UpjGEEHUS+ekVvOaIPR20qidA== Received: from AM7P189MB0807.EURP189.PROD.OUTLOOK.COM (2603:10a6:20b:115::19) by AM8P189MB1282.EURP189.PROD.OUTLOOK.COM (2603:10a6:20b:24c::21) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.23; Sun, 27 Sep 2026 21:29:48 +0000 Received: from AM7P189MB0807.EURP189.PROD.OUTLOOK.COM ([fe80::744:fbdb:56c3:26f5]) by AM7P189MB0807.EURP189.PROD.OUTLOOK.COM ([fe80::744:fbdb:56c3:26f5%6]) with mapi id 15.21.0451.022; Sun, 27 Sep 2026 21:29:48 +0000 From: Kyle Swenson To: "o.rempel@pengutronix.de" , "kory.maincent@bootlin.com" , "andrew+netdev@lunn.ch" , "davem@davemloft.net" , "edumazet@google.com" , "kuba@kernel.org" , "pabeni@redhat.com" , "robh@kernel.org" , "krzk+dt@kernel.org" , "conor+dt@kernel.org" CC: Kyle Swenson , "netdev@vger.kernel.org" , "devicetree@vger.kernel.org" , "linux-kernel@vger.kernel.org" , =?iso-8859-1?Q?Roland_Kov=E1cs?= , =?iso-8859-1?Q?David_Nystr=F6m?= Subject: [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller Thread-Topic: [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller Thread-Index: AQHdTsdSZejWFg7FL0SFdqli66xuKw== Date: Sun, 27 Sep 2026 21:29:48 +0000 Message-ID: <20260927212929.593366-2-kyle.swenson@est.tech> References: <20260927212929.593366-1-kyle.swenson@est.tech> In-Reply-To: <20260927212929.593366-1-kyle.swenson@est.tech> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: mx.microsoft.com 1; dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=est.tech; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: AM7P189MB0807:EE_|AM8P189MB1282:EE_ x-ms-office365-filtering-correlation-id: fffaa411-2e84-41bb-4f00-08df1cde754d x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|1800799024|23010399003|366016|7416014|376014|38070700021|11063799006|56012099006|3023799007|10067099003|921020|18002099003|22082099003; x-microsoft-antispam-message-info: CeVxH+jKPFoQB0RXIoW7iF+8oyyeAK6JlE5Mm5nyZL0QSn7Bx3GAQyZezphKErS9OroqMC1yeyVFnKmzUX/nvOglXHfnoy27QkfYYllT5pMTlzQIU+CfWspTiW533NqT/aLiIZqvMhcUCXZiwUKR66MVBAE0Y48Mio6/Sn5XfzhRsOcm82S758opx7HhGadDWLra3QK4eTRdisgb2jApeL/AOCDFmE2Q1ynu3MKLYLonzfCFlFU3Lc8Y79BGQI5dbcVe0baxcQM84nttXWJOMZ2YgYfCYrW9hY/ZurncWrg66ZJLgHAJcAwIWd/mNcWkiWjawJmLY69DM+pF22RVOYEG7cBcMu+FWs9lzciHbePE0F+lFYkXl541QFjUTzibhL7eFr2cLWDMaVt/G9Oe9ctLKyPWJsK303GCK6Gtu/zkfzs/TgIyFefO4GWk1LYmvo+M2Q2jkbR4Z28XEjrxAkPgkCTXGIGFayfDOgAIUTSkrWeoRc6m7etcQFEgdMZn79/4WQEDBAJH0JkG8/YfyM4MHhDLqm+N+mF4rYnkLcaXm1OKei8N8Il+DVHsp9u6+xU0BZ/VtS9bwIeU3hKV82Gfd5GDogw71j9Hph6UPXUVFIND8IysVydVBmQi1oKDY5cIsb8EpeIZqwlxvazOzccetsdzCVTB7hFUIyuG+nhXSfZZSFsUBLHAU8ePJL7B x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:AM7P189MB0807.EURP189.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(1800799024)(23010399003)(366016)(7416014)(376014)(38070700021)(11063799006)(56012099006)(3023799007)(10067099003)(921020)(18002099003)(22082099003);DIR:OUT;SFP:1101; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?iso-8859-1?Q?vCoEbboILmtyN4rXiZsrQhq0NaJe8u2a+QYniR2B49bHfchR3wOYF3hsp3?= =?iso-8859-1?Q?MgawIUFOYSf0Cupfh+sPswKrb8voDJgx7B28ZXtiQsebRGruPPTU0LTrZY?= =?iso-8859-1?Q?Yre6NcMzb/j3Omx6FisR13dZQT9eSEsXXCLOfleA/cFXKyy7T8aS9F/b7H?= =?iso-8859-1?Q?FjJSoGwS3KkoSAB1AVqJqAZO4qqJgGowiONOkYyRe6a/76pHIO0lRm+HC0?= =?iso-8859-1?Q?Uw2JazHI6QKPkRDqGGJcRPAiVQQNGs4Kz0Wjqxth7Dks8UfGLcjEnMocu5?= =?iso-8859-1?Q?sygMOhPWNwnAxwg6iH4XyX4Xnov95clhtRkI1H31BCVebmXZRVWfBOcss7?= =?iso-8859-1?Q?rV9jS8RAUA72zSoodPJWLiTGSYZIRgvdJMvWEh6Wb1duJB9rsSPRh8haTj?= =?iso-8859-1?Q?wjd+hMr+SVXa3mCWpYviPc/PzZOjE6dW7tQV71usoicBXEv1mcELFIZAiU?= =?iso-8859-1?Q?CFn/mPLziNABsZcjFIvZjDlAwtsLedDFc0vSTO0bhPu8e7EOMmPFFOV1Xh?= =?iso-8859-1?Q?aenrgsDW5ImNy5Te/qzpvouoVST/WO7FwnzH9DZ/z33NvH530+gVFgphhB?= =?iso-8859-1?Q?z2BZzgXEhBWWnCyR16OAaUYUOMw1H2z31vvt121WbKSd0E89TZJ8JxcogG?= =?iso-8859-1?Q?JgoHAzRms38UTDIt18H2F0kNRmXbpBX2aFbX7Td+jCOYkJOFITWfR7a0y2?= =?iso-8859-1?Q?A5T0lO7stA42KSGUNEzhxJfaJ4cybhsBdJmBlnRodG1/UYRkD4WwWyMOZ3?= =?iso-8859-1?Q?q7B+f83XNEMlhDY+g2f/buMe+5mC8sVd1rbeMYIFuGjAQaH796XDfHoPmW?= =?iso-8859-1?Q?TgrVzMK0912UKwmZeZ4/YpkBC1Nobf4DUF6hp+lHQ1ESgUDCjbZvk9bqJE?= =?iso-8859-1?Q?LMMTKsGgIkfKgkhW8/LP5dlLgqGw++/qAfSpmzXmLs0ByteWquP/6QHif3?= =?iso-8859-1?Q?FKU3aCSyROJFg4Vp1s42kPzTOYm4DTqBU/tYmkRJS7Jd0FzsYihD6BjavS?= =?iso-8859-1?Q?B5Jf6N5CiIBTcuT7DwkXQS32v5T1Bg5A8N0xZ82dzk3n3X6SlJ9zM4q6G8?= =?iso-8859-1?Q?c2POuKTN+1HyyhoAee6PWywfznXQRIiZA98sKMNhfagOVNzbfvCBAHoyHQ?= =?iso-8859-1?Q?/C4MePaEcmWbue51afLBNHz5eCJALVSSyaFoxdlSIEChBDbSxEePxq+c50?= =?iso-8859-1?Q?AcXKTAUhcjXC42ls/zC/xBMrS9t82F6bf6FiiTDxNadMcgeWCKu0Z9HJxg?= =?iso-8859-1?Q?1tbL8gn0o8DaLMyrZ/q93QGRzzoo33L6jDlHK0028zh6gc8XpWvjBXj1hp?= =?iso-8859-1?Q?lPtUTk66vZI75B1y30XieIOI5XWw1iQa5/3ThiNqY/MoD1gO+52wZW4sMB?= =?iso-8859-1?Q?bH0ubschBNWkZATeLCSX286g4hnScZ8el70y5R1Uh+q+eNiSHp//9JdDKa?= =?iso-8859-1?Q?ZITknxxjKQkDEBM9S0O3UgWyBQQhbF9zVpoLs80O5B3tOiXoNNPtVUVKeK?= =?iso-8859-1?Q?KbFK7PhbVzT3GUrViImnjtRtoDAUpZ0CkRMD7aDqRUukS4r+gkXf8PJUx2?= =?iso-8859-1?Q?qE3NH9SryMVa1mYkfpS6AdlIre9VCmBSWpiJesfBFFM66KJYDYQYNYVa3c?= =?iso-8859-1?Q?dDGSyppKuns1YMnpeHM+mzWALp9+rZz1DJ6ih59CUDhyvDTvUaSLOYfISe?= =?iso-8859-1?Q?BV89WGDPLL1O8rxMFcpkLvY6HQXzGN/cetiSCRXr+g+UFRCKKyNQMsugKz?= =?iso-8859-1?Q?2xe/mFYhwal2S4rowKdtbwDCXoVyErI/op4XiSHtpcXrH2icVZsrl/PYzf?= =?iso-8859-1?Q?LaanFKz5YA=3D=3D?= Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-OriginatorOrg: est.tech X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: AM7P189MB0807.EURP189.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-Network-Message-Id: fffaa411-2e84-41bb-4f00-08df1cde754d X-MS-Exchange-CrossTenant-originalarrivaltime: 27 Sep 2026 21:29:48.6184 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: d2585e63-66b9-44b6-a76e-4f4b217d97fd X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: ed70YNLKsFdtjgfkCb0MePXWhI5UEXXHPm490Wx2krjmjGiAc5URYJ+RPDdo6TmaYJssP5YlFQX8VyXvTLpEoQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: AM8P189MB1282 Content-Type: text/plain; charset="utf-8" Add the LTC4266 Power Sourcing Equipment controller device tree bindings documentation. Signed-off-by: Kyle Swenson --- .../bindings/net/pse-pd/lltc,ltc4266.yaml | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/pse-pd/lltc,ltc42= 66.yaml diff --git a/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml= b/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml new file mode 100644 index 000000000000..e11c9d601eee --- /dev/null +++ b/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml @@ -0,0 +1,180 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/pse-pd/lltc,ltc4266.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Linear Technology LTC4266 Power Sourcing Equipment controller + +maintainers: + - Kyle Swenson + +allOf: + - $ref: pse-controller.yaml# + +properties: + compatible: + enum: + - lltc,ltc4266 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + channels: + type: object + additionalProperties: false + description: + Defines the 4 physical delivery channels on the controller that can = be + referenced by PSE PIs through their "pairsets" property. The actual = port + matrix mapping is created when PSE PIs reference these channels in t= heir + pairsets. + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + patternProperties: + '^channel@[0-3]$': + type: object + additionalProperties: false + + properties: + reg: + maxItems: 1 + + sense-resistor-micro-ohms: + description: Sense resistor connected to the channel's MOSFET,= used + for current measurement and for overcurrent detection. The I= _CUT + and I_LIM register encodings depend on which value is fitted= , so + a wrong value programs the wrong thresholds. + enum: [250000, 500000] + + required: + - reg + - sense-resistor-micro-ohms + + required: + - '#address-cells' + - '#size-cells' + + pse-pis: + type: object + additionalProperties: false + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + patternProperties: + '^pse-pi@[0-3]$': + type: object + additionalProperties: true + properties: + pairsets: + description: The LTC4266 delivers power to a PI over a single + pairset, driven by one of the controller's four channels. Th= ere + is no 4-pair mode spreading a PI over two channels, so exact= ly + one channel phandle is expected. + maxItems: 1 + pairset-names: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - channels + - pse-pis + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + ethernet-pse@2f { + compatible =3D "lltc,ltc4266"; + reg =3D <0x2f>; + interrupts =3D <8 IRQ_TYPE_LEVEL_LOW>; + interrupt-parent =3D <&gpio>; + + channels { + #address-cells =3D <1>; + #size-cells =3D <0>; + + phys0: channel@0 { + reg =3D <0>; + sense-resistor-micro-ohms =3D <500000>; + }; + + phys1: channel@1 { + reg =3D <1>; + sense-resistor-micro-ohms =3D <500000>; + }; + + phys2: channel@2 { + reg =3D <2>; + sense-resistor-micro-ohms =3D <500000>; + }; + + phys3: channel@3 { + reg =3D <3>; + sense-resistor-micro-ohms =3D <500000>; + }; + }; + + pse-pis { + #address-cells =3D <1>; + #size-cells =3D <0>; + + pse_pi0: pse-pi@0 { + reg =3D <0>; + #pse-cells =3D <0>; + pairset-names =3D "alternative-a"; + pairsets =3D <&phys0>; + polarity-supported =3D "MDI"; + vpwr-supply =3D <&vreg_pse>; + }; + + pse_pi1: pse-pi@1 { + reg =3D <1>; + #pse-cells =3D <0>; + pairset-names =3D "alternative-a"; + pairsets =3D <&phys1>; + polarity-supported =3D "MDI"; + vpwr-supply =3D <&vreg_pse>; + }; + + pse_pi2: pse-pi@2 { + reg =3D <2>; + #pse-cells =3D <0>; + pairset-names =3D "alternative-a"; + pairsets =3D <&phys2>; + polarity-supported =3D "MDI"; + vpwr-supply =3D <&vreg_pse>; + }; + + pse_pi3: pse-pi@3 { + reg =3D <3>; + #pse-cells =3D <0>; + pairset-names =3D "alternative-a"; + pairsets =3D <&phys3>; + polarity-supported =3D "MDI"; + vpwr-supply =3D <&vreg_pse>; + }; + }; + }; + }; --=20 2.55.0 From nobody Mon Sep 28 16:21:19 2026 Received: from MRWPR03CU001.outbound.protection.outlook.com (mail-francesouthazon11011037.outbound.protection.outlook.com [40.107.130.37]) (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 1EDD53C7691; Sun, 27 Sep 2026 21:29:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=40.107.130.37 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790544598; cv=fail; b=h3BsacOvxuDVDrux81aT83/ifYeBKXBegXyIlHnfPIQsW8WlS4gSX3DvR9KUcaxXH2D8uGp82Bl2g9tIPhZNmOaK/ZJSt/0XukKyQlHqaYqxcIFCU//B8T6uVtiDoOCTUZxkSlf5GJQd1JUVhN1NsbrDdUQ8XUGFPDcAM74rh4E= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790544598; c=relaxed/simple; bh=KKiyzuWuK/bDsOqr1cWCz0z4YI5thtQrurJwynspIro=; h=From:To:CC:Subject:Date:Message-ID:References:In-Reply-To: Content-Type:MIME-Version; b=X5o9N42ok/EWLsqcLp54Xr9G9YHypSuzGVYUnHrBUrTQqpBfjXLrqnin8EyjaMBeEOIFvNpr558q+ahDE7rM6zUQ1RuFmup2XVWLmlsFROwZeIOqOjoKs+BErM2/2AxnQk5X5MGJSaHuUioBH7B8g1+OrDLgh22KrvcsTE0R+cM= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=est.tech; spf=pass smtp.mailfrom=est.tech; dkim=pass (2048-bit key) header.d=est.tech header.i=@est.tech header.b=adbGraR6; arc=fail smtp.client-ip=40.107.130.37 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=est.tech Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=est.tech Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=est.tech header.i=@est.tech header.b="adbGraR6" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=J5hNpazZmbLcm2+0l4UPfp6wq6O66lCtrVXfTPrBlDgUSVU2J6RWOh67AGE+sxM7jO/riGG/h25cL7/AO3BFC1dlIdMCjpMtZU076Y456sZkvsjhjf82GxmFaHdr1HQTtYCKyqj8i1N0P3GFg6/5s9ohGVD1HI77B77dTE9EOdi1rumQNzXT5sAk2XecpLTLgsOxuWi6fv4jV0yOdJFWTDSeiqtLftcqI3741HCyGCKs7WD8GlD2E2LGWGQgjkNMGcG5uQ+h3elMWbupTpK0W7f0OKARLpgSKVOd79WclRgilzj2Sx/ZEEBmNEUHXmfF4ZgjAkUODcL2GPTbqPYmWQ== 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=vPsnB1hC1ocdN1/qXp3JIDIyoU+2+DnYwA/a5FZZVj8=; b=jlEbdUyJFr0oJTNKXRRGRcprwxkWGBcKKg9K9hMx+mbOaDwfEEGAmJVRPnAw/1LidsVwU7pdXO3TVA5nrT/dzLgnHfvi39CLIaCpbvw2/8ys47dKVfYI/Db04U06EybpDpBmkB9FgACO2tbfhKFVbBti/RFoJ2/9fD3lzf9jJSxTlZVLhRNcFzMsrGv5WWO7qHeMqIrEjmr0D/RMFTZ5+3Rn4rWDMneHWIkXu14EwEApemZw3qw9COm3vCN0U1UQ90sDA6TIF+08xClIPPY4QR2BmjqqBadsmK/VVczEj6ratHRtdiAJ3kgPpME91xXOUHjP+2v0qVYjB+V0bGiLnA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=est.tech; dmarc=pass action=none header.from=est.tech; dkim=pass header.d=est.tech; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=est.tech; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=vPsnB1hC1ocdN1/qXp3JIDIyoU+2+DnYwA/a5FZZVj8=; b=adbGraR6Ngxf3bkSfxx2KZbK7zMEFy0Ve8019OcUSMQhpKmMDA4fkU9r84QULj5XTH/Rg9vq0ZTboMiCsHjMUiG1AbChP9QcbzQnJWgxIZofAKpvSWMRzwMVErU/FUAs2mSnkKrkZdMuzNDiqzk5F3kG5ut4U0gbeCFLNkmZcE12DIfK50vOw2bGsj0CBWodQsdFwumSB1+k+LdoIC7ppQ6CX0TatxZAZBEo+h6+YqxIOIm2u5xcQk2FdlD09NSa31ZnoTYlISH81GsE+4BToL8QWHmc3faW/+Od59qPfROrwGMtMet9q1BoIKXVE5KaMLOxnjFTeP6pC0/mkFBbjQ== Received: from AM7P189MB0807.EURP189.PROD.OUTLOOK.COM (2603:10a6:20b:115::19) by AM8P189MB1282.EURP189.PROD.OUTLOOK.COM (2603:10a6:20b:24c::21) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.23; Sun, 27 Sep 2026 21:29:51 +0000 Received: from AM7P189MB0807.EURP189.PROD.OUTLOOK.COM ([fe80::744:fbdb:56c3:26f5]) by AM7P189MB0807.EURP189.PROD.OUTLOOK.COM ([fe80::744:fbdb:56c3:26f5%6]) with mapi id 15.21.0451.022; Sun, 27 Sep 2026 21:29:51 +0000 From: Kyle Swenson To: "o.rempel@pengutronix.de" , "kory.maincent@bootlin.com" , "andrew+netdev@lunn.ch" , "davem@davemloft.net" , "edumazet@google.com" , "kuba@kernel.org" , "pabeni@redhat.com" , "robh@kernel.org" , "krzk+dt@kernel.org" , "conor+dt@kernel.org" CC: Kyle Swenson , "netdev@vger.kernel.org" , "devicetree@vger.kernel.org" , "linux-kernel@vger.kernel.org" , =?iso-8859-1?Q?Roland_Kov=E1cs?= , =?iso-8859-1?Q?David_Nystr=F6m?= Subject: [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver Thread-Topic: [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver Thread-Index: AQHdTsdUN3oGrZucsUuwa7XeBfac2g== Date: Sun, 27 Sep 2026 21:29:51 +0000 Message-ID: <20260927212929.593366-3-kyle.swenson@est.tech> References: <20260927212929.593366-1-kyle.swenson@est.tech> In-Reply-To: <20260927212929.593366-1-kyle.swenson@est.tech> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: mx.microsoft.com 1; dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=est.tech; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: AM7P189MB0807:EE_|AM8P189MB1282:EE_ x-ms-office365-filtering-correlation-id: 17099ffc-9035-4b69-4921-08df1cde76cd x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|1800799024|23010399003|366016|7416014|376014|38070700021|11063799006|56012099006|5023799004|3023799007|10067099003|260925022911599003|260925021311599003|260925021911599003|6133799003|921020|18002099003|22082099003; x-microsoft-antispam-message-info: d1Sb7I52yAXpmOAzJIt3hyaxL6M7y1KO9boPg/8mr37RGLNKkdiLLEQ4zHzllL9dVcu+cXr7H4zlOJ69IO736WKWlKviQcT9LokiwdhkaXk7R83MRIcYB8fkoIxE8I6jGMDsF4FNdqtq4qnC3ZoJiqgcNdVMYEugmlASWi9aFwi7OIRVHqqB+qtqLqWIDVin3wLgqK/yA0ZesXSRbQYRhjMpQ8QzwKdqutbOCJtebTcQeLl7ZIMoFJM9csYoMsfac5mEu1/qXF+FgznRvzTrpE9Lrnn+q5lDTADYf4/dUc+sS7Etz+7Z3uC5XSJ1sMHgSDN8LhxaYSIQ8uUL6oJ+BdSOVwJlJZCDj1d6jIRGiZ25OmV8CFAdZZxoPzEP3WJ3Cs69rCGTLbPUTV8H8oeK/CLk7yRzMuRDGczUNCWa8naOnjJR+dCwMfCwNmMBkUmMSX/osNBXXksnW3sx2Cmk+ye5aCiEKapNHYxllYGPqjGbzClrWYFq4VKOfJc0QU0VqZjgHclMKoXlvvIi3t/sp98TE30xnNBej6dz6rnL4Qav70s0CGrRTfL/uo+4LgiDYawQ2OVCKRhfx7icv09ncxU1nce4kby1Jb4w0pjZmoD/3NuMd2smccnFfcu2N3HLAgAIQz1OuZottMzmR30clzx77BGC46tRUVrYDWdwU8/+qeFlESEijPsV0wQpK8flLnQxgMbgDUBcOSu4oZA0cIdzqRTSw6ZgXE7k/YuHWOcknvnw24N0BAxF22rEoJS1 x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:AM7P189MB0807.EURP189.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230040)(1800799024)(23010399003)(366016)(7416014)(376014)(38070700021)(11063799006)(56012099006)(5023799004)(3023799007)(10067099003)(260925022911599003)(260925021311599003)(260925021911599003)(6133799003)(921020)(18002099003)(22082099003);DIR:OUT;SFP:1101; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?iso-8859-1?Q?c+cdmU9OXpREJdNBEP60/xbHrt2JtZXL+CCd8N8HdCUmQB4gP6fpnpweR5?= =?iso-8859-1?Q?rhfcyCrme5NSkUThnX71oZP50nttI1bGTH2wGoP4I22F/3DgEU4hhONjtO?= =?iso-8859-1?Q?51of6RRryPfQeDDXgZrnYbtEZEQxoCr4IAuVuzqN1USNfjbSefgmtwh70Z?= =?iso-8859-1?Q?SpIu2ZwCA2nDCu87jzxQwnJJtQUgAa/6B32n2KLDHixjvl92yDeQWof2az?= =?iso-8859-1?Q?n9Q06roq7oCrsPegPwag3a3+MLq2mYJqns+4Y6uVKozlvC6zzdYkBUT5/6?= =?iso-8859-1?Q?2PPmBb/5y8PaguI3HxgBoOfJaxW6CQ67o1aZHOS1tS6muApDAHJorEngN0?= =?iso-8859-1?Q?F11u7ek+0/4/24WWQ80wGd6rilXFCD1lr8shT4lbCVe2RQKemMX9z8tjMM?= =?iso-8859-1?Q?XxlZcVbFjBcRsv/Da15zfzJyd3Eyy3wuQ9NPa4rc8zSboXkkl7fEiR4j7g?= =?iso-8859-1?Q?VTUIOhrzjdx3DnXCcyU75Xr5Z7jlNeXecv0gFvPCSapffiCweTKAj9XTJY?= =?iso-8859-1?Q?O7KWviJPktJRgMaB9nuikvwJ3HiAT6WXAJRiMaLdPL81Zs5JwVdOgE2tOt?= =?iso-8859-1?Q?pEj/Qy3wBd16Ho0Dx9tigWYZEetZRZmqVa2LXZbhVrwYvO9Ji+sZwxr4Pp?= =?iso-8859-1?Q?akmpoccnJkmYg7yOaAJcRJnSzEvqABtqxnsB6gGngZx0WWwkSVdOr+i3ab?= =?iso-8859-1?Q?47AhEXXy/vV68wCD6RKJee8O9yhpTQzxPiNQXVQVm0AQFhhzFQtt1wnhTo?= =?iso-8859-1?Q?qiIbsa79aCT0IoNk9SdsLjnv+3Kxl+2Zv+Wk2kAtbv4mz4rr8b3sm8nFqH?= =?iso-8859-1?Q?4woGAtnVZ/iP58cMK+CS1NSYZXaxJPSd0IB81n7WlC5szh8v9BhynvaLn/?= =?iso-8859-1?Q?8yDn40iPGY1ixEKTiSBmSbTaDIHH8fkYKNA7dUP/vEsy5p0ukGLkROMzYP?= =?iso-8859-1?Q?hbBI6K2n7M0SKXjlK23AIc5cP1uwauK545aNn+SO6XK28uOgGyMwIACNWX?= =?iso-8859-1?Q?p3aSnNR7Ob/n0efYGflPRlvYt5WTAc4sX76buzIm5e5ZsZOunY07xi8l5J?= =?iso-8859-1?Q?v738u4wgHVxPJ1RwhfDHfIOD0n9cxaQQeOyKM5G1A3Czxj/9WhHfqLzm68?= =?iso-8859-1?Q?PySRH+TjBhgQlAMXcGQjGNx3z7geoKeLnEE9pdvLZSwr2PTplK/Br3I2DP?= =?iso-8859-1?Q?6QOPQ52pF+wni/4zJ3fv++mI9i58c0ItfAk3HXHFEmZCfYmMYj50OqVNe3?= =?iso-8859-1?Q?M4X70NlLiydGtLvn2bMC0pZhbDp23iNUh5SUcpE8o3Spb938X8VNFfbU3H?= =?iso-8859-1?Q?nNCf8B4tqdNYcRSmN6hanK21BEnlihQLm6CeeDXZ1WKxSVMCcZ3e95WVpm?= =?iso-8859-1?Q?u/aB9fzCWSS953DltcRYqO6qXYb8RqsH1l8qY61E/vMNkCTiVFeZtdZ7Qe?= =?iso-8859-1?Q?cQcirrjs1SqT8WvTjrPoff6V82PsbCdpNJ7tAYr8yB2hL9cp6WgBAk3hd9?= =?iso-8859-1?Q?SimojvzkyM8ww0/Ia6qN6WXTFWOtopPSVj/xvvzzSHOEA22brDh8aRns+t?= =?iso-8859-1?Q?TlYfyVZlLMfRp5dbrIu30IHmZXst6BoJAa1eSBAqBnJp8J0bjDtEneGrL7?= =?iso-8859-1?Q?frVZQBWzylWEwt4x5yyNSakkVMc+Jihu6VZ2DRMIdAGg3QRsHYjJVjhVAS?= =?iso-8859-1?Q?05LuZSv+swg4jTV5vpRHQ9MZNcJZwT1vUOYT0qXUoLZrnKkdz5LdL5I57Q?= =?iso-8859-1?Q?9jnNQrkuKIAC8qV4WvGZJAIX7HDRL91cEq7cZS5IVfdRt5oMizcsBotKku?= =?iso-8859-1?Q?B0H5z1cD8Q=3D=3D?= Content-Transfer-Encoding: quoted-printable Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-OriginatorOrg: est.tech X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: AM7P189MB0807.EURP189.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-Network-Message-Id: 17099ffc-9035-4b69-4921-08df1cde76cd X-MS-Exchange-CrossTenant-originalarrivaltime: 27 Sep 2026 21:29:51.0856 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: d2585e63-66b9-44b6-a76e-4f4b217d97fd X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: 7rAFzBEvT7LTHN1aUnjUHi1aGysOuCWp78WkQ4t0WMi7E6qXMVS55y4a65jO5LCm0FTBzaPFSaiA3TxVCc/bxw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: AM8P189MB1282 Content-Type: text/plain; charset="utf-8" Add a new driver for the Linear Technology LTC4266 I2C Power Sourcing Equipment controller and integrate with the PSE controller core. The LTC4266 supports Type 1 and Type 2 Powered Devices. Signed-off-by: Kyle Swenson --- MAINTAINERS | 7 + drivers/net/pse-pd/Kconfig | 11 + drivers/net/pse-pd/Makefile | 1 + drivers/net/pse-pd/ltc4266.c | 1386 ++++++++++++++++++++++++++++++++++ 4 files changed, 1405 insertions(+) create mode 100644 drivers/net/pse-pd/ltc4266.c diff --git a/MAINTAINERS b/MAINTAINERS index 6de1ff058db6..3f7226d04983 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15531,10 +15531,17 @@ L: linux-leds@vger.kernel.org S: Maintained W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/leds/adi,ltc3220.yaml F: drivers/leds/leds-ltc3220.c =20 +LTC4266 PSE CONTROLLER DRIVER +M: Kyle Swenson +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml +F: drivers/net/pse-pd/ltc4266.c + LTC4282 HARDWARE MONITOR DRIVER M: Nuno Sa L: linux-hwmon@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig index a0f2ae668c67..844c1b237df7 100644 --- a/drivers/net/pse-pd/Kconfig +++ b/drivers/net/pse-pd/Kconfig @@ -46,10 +46,21 @@ config PSE_REGULATOR help This module provides support for simple regulator based Ethernet Power Sourcing Equipment without automatic classification support. For example for basic implementation of PoDL (802.3bu) specification. =20 +config PSE_LTC4266 + tristate "LTC4266 PSE controller" + depends on I2C + select REGMAP_I2C + help + This module provides support for LTC4266 regulator based Ethernet + Power Sourcing Equipment. + + To compile this driver as a module, choose M here: the + module will be called ltc4266. + config PSE_PD692X0 tristate "PD692X0 PSE controller" depends on I2C select FW_LOADER select FW_UPLOAD diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile index 9cca5900fe34..c3fe4efcf1de 100644 --- a/drivers/net/pse-pd/Makefile +++ b/drivers/net/pse-pd/Makefile @@ -1,10 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-only # Makefile for Linux PSE drivers =20 obj-$(CONFIG_PSE_CONTROLLER) +=3D pse_core.o =20 +obj-$(CONFIG_PSE_LTC4266) +=3D ltc4266.o obj-$(CONFIG_PSE_REALTEK_MCU) +=3D realtek-pse-mcu-core.o obj-$(CONFIG_PSE_REALTEK_MCU_I2C) +=3D realtek-pse-mcu-i2c.o obj-$(CONFIG_PSE_REALTEK_MCU_UART) +=3D realtek-pse-mcu-uart.o obj-$(CONFIG_PSE_REGULATOR) +=3D pse_regulator.o obj-$(CONFIG_PSE_PD692X0) +=3D pd692x0.o diff --git a/drivers/net/pse-pd/ltc4266.c b/drivers/net/pse-pd/ltc4266.c new file mode 100644 index 000000000000..37dea467811a --- /dev/null +++ b/drivers/net/pse-pd/ltc4266.c @@ -0,0 +1,1386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Linear LTC4266 PoE PSE Controller + * + * Original work: + * Copyright 2019 Cradlepoint Technology, Inc. + * Cradlepoint Technology, Inc. + * + * Re-written in 2026: + * Copyright 2026 Ericsson Software Technology + * Kyle Swenson + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC4266_MAX_PORTS 4 + +/* The minimum and maximum here depend on the resolution of the I_CUT fiel= d, + * which is 18.75mA. To get 1000mW with a 50V port voltage, we need 20mA;= but + * with 18.75 mA steps we end up with a current limit of 37.5 mA resulting= in + * an actual power limit of 1875mW. + */ +#define LTC4266_PW_LIMIT_MAX 30000 +#define LTC4266_PW_LIMIT_MIN 1000 + +/* Nominal PI voltage reported when the port is not delivering power and no + * "vpwr-supply" is described in the device tree: V_Port_PSE min for a Typ= e 2 + * PSE. IEEE 802.3-2022 Table 33-11 item 1 specifies the POWER_ON state ou= tput + * voltage as 50.0 V to 57.0 V for a Type 2 PSE, 44.0 V to 57.0 V for Type= 1. + */ +#define LTC4266_VPORT_NOMINAL_UV 50000000 + +/* Register definitions */ +#define LTC4266_REG_INTSTAT 0x00 +#define LTC4266_REG_INTMASK 0x01 +#define LTC4266_REG_PWREVN_COR 0x03 +#define LTC4266_REG_DETEVN_COR 0x05 +#define LTC4266_REG_FLTEVN_COR 0x07 +#define LTC4266_REG_TSEVN_COR 0x09 +#define LTC4266_REG_SUPEVN_COR 0x0B +#define LTC4266_REG_STAT(_p) (0x0C + (_p)) +#define LTC4266_REG_STATPWR 0x10 +#define LTC4266_REG_OPMD 0x12 +#define LTC4266_REG_DISENA 0x13 /* Disconnect detect enable */ +#define LTC4266_REG_MCONF 0x17 +#define LTC4266_REG_DETPB 0x18 /*PB means "push button" */ +#define LTC4266_REG_PWRPB 0x19 +#define LTC4266_REG_RSTPB 0x1A +#define LTC4266_REG_ID 0x1B +#define LTC4266_REG_TLIM12 0x1E +#define LTC4266_REG_TLIM34 0x1F +#define LTC4266_REG_IPLSB(_p) (0x30 | ((_p) << 2)) +#define LTC4266_REG_VPLSB(_p) (LTC4266_REG_IPLSB(_p) + 2) +#define LTC4266_REG_HPEN 0x44 +#define LTC4266_REG_HPMD(_p) (0x46 + (5 * (_p))) +#define LTC4266_REG_ICUT_HP(_p) (LTC4266_REG_HPMD(_p) + 1) +#define LTC4266_REG_ILIM(_p) (LTC4266_REG_HPMD(_p) + 2) + +/* Register field definitions */ + +/* LTC4266_REG_INTSTAT and LTC4266_REG_INTMASK */ +#define LTC4266_INT_TSTART BIT(6) +#define LTC4266_INT_TCUT BIT(5) +#define LTC4266_INT_CLASS BIT(4) +#define LTC4266_INT_DETECT BIT(3) +#define LTC4266_INT_DIS BIT(2) +#define LTC4266_INT_PWRGD BIT(1) + +/* Per-port event bits within the CoR event registers. Every per-port event + * register splits its 8 bits into a per-port low nibble and a per-port hi= gh + * nibble: + * pwrevn (03h): LO =3D power on/off change, HI =3D power good change + * detevn (05h): LO =3D detection complete, HI =3D classification complete + * fltevn (07h): LO =3D tCUT overcurrent, HI =3D tDIS DC disconnect + * tsevn (09h): LO =3D tSTART overcurrent, HI =3D tLIM current-limit time= out + */ +#define LTC4266_EVN_HI(_p) BIT((_p) + 4) /* ports 0-3 */ +#define LTC4266_EVN_LO(_p) BIT(_p) /* ports 0-3 */ + +/* statp (0Ch-0Fh) detection result, and the "Signature Good" value */ +#define LTC4266_PORT_CLASS(_stat) FIELD_GET(GENMASK(6, 4), (_stat)) +#define LTC4266_PORT_DETECT(_stat) FIELD_GET(GENMASK(2, 0), (_stat)) +#define LTC4266_DETECT_GOOD 0x4 + +/* LTC4266_REG_STATPWR */ +#define LTC4266_STATPWR_PG(_p) BIT((_p) + 4) +#define LTC4266_STATPWR_PE(_p) BIT(_p) + +/* LTC4266_REG_OPMD + * There are three other operation modes possible this + * driver doesn't support and so aren't defined. The one supported mode, + * OPMD_SEMI, means that a port will continuously detect and classify devi= ces, + * but will not power the device until instructed to do so. + */ +#define LTC4266_OPMD_SEMI 2 +#define LTC4266_TWO_BIT_WORD_OFFSET(_v, _p) ((_v) << ((_p) * 2)) +#define LTC4266_TWO_BIT_WORD_MASK(_p) LTC4266_TWO_BIT_WORD_OFFSET(0x03, (= _p)) + +/* LTC4266_REG_MCONF */ +#define LTC4266_MCONF_INTERRUPT_ENABLE BIT(7) +/* Only report a detect event when the result changes, not every cycle */ +#define LTC4266_MCONF_DETCHG BIT(6) + +/* LTC4266_REG_DETPB */ +#define LTC4266_DETPB_CLASS_ENABLE(_p) BIT((_p) + 4) +#define LTC4266_DETPB_DETECT_ENABLE(_p) BIT(_p) + +/* LTC4266_REG_RSTPB */ +#define LTC4266_RSTPB_INTCLR BIT(7) +#define LTC4266_RSTPB_PINCLR BIT(6) +#define LTC4266_RSTPB_RSTALL BIT(4) +#define LTC4266_RSTPB_RSTPORTS GENMASK(3, 0) + +/* LTC4266_REG_ID */ +#define LTC4266_ID 0x64 + +/* LTC4266_REG_TLIM* */ +#define LTC4266_TLIM_VALUE 0x01 + +/* Current-sense scaling, in nA per LSB, dependent on the sense resistor. = */ +#define LTC4266_IP_NA_PER_LSB_RSENSE_025 122070 /* 122.07 uA/LSB */ +#define LTC4266_IP_NA_PER_LSB_RSENSE_050 61035 /* 61.035 uA/LSB */ + +/* Voltage-sense scaling: 5.835 mV =3D=3D 5835 uV per LSB. */ +#define LTC4266_VP_UV_PER_LSB 5835 + +/* LTC4266_REG_HPEN, enable "High Power" mode (Type 2, Class 4) */ +#define LTC4266_HPEN(_p) BIT(_p) + +/* LTC4266_REG_HPMD */ +#define LTC4266_HPMD_PONGEN 0x01 + +/* LTC4266_REG_ICUT_HP. + * Set if the sense resistor specified in DT is 0.25 Ohm to have accurately + * scaled ICUT thresholds. + */ +#define LTC4266_ICUT_RSENSE_025_OHM BIT(7) + +/* To keep the ICUT resolution at a constant 18.75 mA, for the 0.25 Ohm se= nse + * case we should also set this ICUT_RANGE + */ +#define LTC4266_ICUT_RANGE BIT(6) + +/* I_CUT is programmed in a 6-bit field; each step is 18.75 mA (18750 uA).= */ +#define LTC4266_ICUT_STEP_UA 18750 +#define LTC4266_ICUT_MASK GENMASK(5, 0) + +/* Cap I_CUT at the suggested value for a Type 2 PD at 638mA */ +#define LTC4266_ICUT_MAX_MA 638 +#define LTC4266_ICUT_MAX_STEPS 34 + +/* Recommended lim settings from datasheet Table 5. + * + * I_LIM (mA) RSENSE =3D 0.5 Ohm RSENSE =3D 0.25 Ohm + * 425 (Type 1) 0x00 0x80 + * 850 (Type 2) 0x40 0xC0 + */ +#define LTC4266_ILIM_TYPE1_RSENSE_050 0x00 +#define LTC4266_ILIM_TYPE1_RSENSE_025 0x80 +#define LTC4266_ILIM_TYPE2_RSENSE_050 0x40 +#define LTC4266_ILIM_TYPE2_RSENSE_025 0xC0 + +enum { + LTC4266_READ_CURRENT =3D 0, + LTC4266_READ_VOLTAGE =3D 2 +}; + +/* Map LTC4266 Classification result to PD class. Note for a PD that has a + * valid detect signature, but doesn't produce a classification signature = is + * still a valid PD. The LTC4266 indicates this with 0x06 in the statp + * register and calls it "Class 0". This is a different state than when + * statp indicates 0, which means classification isn't complete. This = maps + * the result to either an errno or classification value suitable for use = up + * the stack. + */ +static const int ltc4266_class_map[] =3D { + -EAGAIN, /* Classification is incomplete */ + 1, + 2, + 3, + 4, + -EINVAL, + 0, + -ERANGE +}; + +/* Map a PD Class to I_CUT thresholds from the LTC4266 datasheet Table 2 */ +static const int ltc4266_class_to_icut[] =3D { + 375, + 112, + 206, + 375, + 638 +}; + +/* Per-class power budget at the PSE PI in mW, indexed by class (0-4). + * Classes 0-3 are the P_Class values from Table 33-7 in the IEEE802.3 + * standard, which Note 1 defines as the minimum power a PSE must supply a= t the + * PI for that class (the PD input power maxima are in Table 33-18). Class= 4 is + * P_Type, which Table 33-11 item 12 defines as I_Cable x V_Port_PSE min: + * 0.600 A x 50.0 V for a Type 2 PSE, with I_Cable from Table 33-1. + * + * Class 0 and Class 3 have the same value because a device without a clas= sification + * signature (Class 0) has to be assumed to consume up to the maximum powe= r for + * a Type 1 PD (Class 3). + */ +static const int ltc4266_class_pw[] =3D { + 15400, /* Class 0 */ + 4000, /* Class 1 */ + 7000, /* Class 2 */ + 15400, /* Class 3 */ + 30000, /* Class 4 (Type 2) */ +}; + +enum sense_resistor { + LTC4266_RSENSE_500, /* Rsense 0.5 Ohm */ + LTC4266_RSENSE_250 /* Rsense 0.25 Ohm */ +}; + +struct ltc4266; + +/** + * struct ltc4266_port - per-PSE-PI context + * + * @ltc4266: the controller owning this port + * @chan: index of the LTC4266 delivery channel backing this PI, establish= ed + * from the PI pairset phandle by ltc4266_map_pis(). All register + * addressing uses this member. + * @rsense: sense resistor on @chan, used to scale current readings and + * to pick the I_CUT and I_LIM encodings. + * @pw_limit: Admin-configured power limit in mW, always valid: it default= s to + * LTC4266_PW_LIMIT_MAX, the most this controller can deliver, until + * pi_set_pw_limit() lowers it. + * @vpwr_uv: nominal PI voltage in uV, read once from the PI's "vpwr-suppl= y" at + * probe, or LTC4266_VPORT_NOMINAL_UV when the PI does not describe + * one. Reported while the port is not delivering power, when the + * controller has nothing to measure. + */ +struct ltc4266_port { + struct ltc4266 *ltc4266; + u8 chan; + enum sense_resistor rsense; + int pw_limit; + int vpwr_uv; +}; + +/** + * struct ltc4266 - LTC4266 controller context + * + * @client: the I2C client + * @regmap: register map of @client + * @ports: table of PSE PI contexts, indexed by PSE PI id. A NULL entry is= a PI + * that is not described in the device tree and therefore has no + * channel mapped. + * @dev: the underlying device + * @np: device node of @dev + * @pcdev: the PSE controller registered with the PSE core + */ +struct ltc4266 { + struct i2c_client *client; + struct regmap *regmap; + struct ltc4266_port *ports[LTC4266_MAX_PORTS]; + struct device *dev; + struct device_node *np; + struct pse_controller_dev pcdev; +}; + +static struct ltc4266_port *ltc4266_pi_port(struct pse_controller_dev *pcd= ev, + int id) +{ + struct ltc4266 *ltc4266 =3D container_of(pcdev, struct ltc4266, pcdev); + + return ltc4266->ports[id]; +} + +static int ltc4266_read_iv(struct ltc4266_port *port, u8 iv) +{ + struct ltc4266 *ltc4266 =3D port->ltc4266; + unsigned int lsb, msb; + unsigned int statpwr; + int lsb_reg; + int result; + u64 ivbits; + + result =3D regmap_read(ltc4266->regmap, LTC4266_REG_STATPWR, &statpwr); + if (result < 0) + return result; + + /* LTC4266 IV readings are only meaningful while the port is delivering + * power. When the PG (power good) bit is not set, the port is + * delivering nothing, so report 0 rather than an error: returning an + * errno here would abort pse_ethtool_get_status() and fail the whole + * "ethtool --show-pse" query for an otherwise perfectly readable port. + * Callers that must not report 0 (ltc4266_port_voltage_uv()) substitute + * a nominal value. + */ + if (!(statpwr & LTC4266_STATPWR_PG(port->chan))) + return 0; + + /* Since iv is either 0 (to read current) or 2 to (read voltage), we + * can get the LSB reg by adding + */ + lsb_reg =3D LTC4266_REG_IPLSB(port->chan) + iv; + + result =3D regmap_read(ltc4266->regmap, lsb_reg, &lsb); + if (result < 0) + return result; + + result =3D regmap_read(ltc4266->regmap, lsb_reg + 1, &msb); + if (result < 0) + return result; + + ivbits =3D (msb << 8) | lsb; + + if (iv =3D=3D LTC4266_READ_CURRENT) + if (port->rsense =3D=3D LTC4266_RSENSE_250) + result =3D DIV_ROUND_CLOSEST_ULL(ivbits * LTC4266_IP_NA_PER_LSB_RSENSE_= 025, + 1000); + else + result =3D DIV_ROUND_CLOSEST_ULL(ivbits * LTC4266_IP_NA_PER_LSB_RSENSE_= 050, + 1000); + else + result =3D ivbits * LTC4266_VP_UV_PER_LSB; + + return result; +} + +/* Voltage at the PI in uV, if the PI is currently powering a device; + * otherwise, return the nominal voltage + */ +static int ltc4266_port_voltage_uv(struct ltc4266_port *port) +{ + int uv =3D ltc4266_read_iv(port, LTC4266_READ_VOLTAGE); + + if (uv < 0) + return uv; + + return uv ? uv : port->vpwr_uv; +} + +/** + * ltc4266_port_set_ilim - Set the active current limit (ILIM) for a port + * @port: the port to configure + * @class: the detected PD class (0-4) + * + * Given the PD class, configure the active current limit for a particular + * channel. The LTC4266 will actively enforce this current limit using the + * sense resistor for the channel. The values written are from the LTC4266 + * Datasheet, Table 5, and correspond to 425mA for Type I PDs, and 850mA f= or + * Type 2 PDs (per the datasheet requirement for IEEE compliance). + * + * IEEE Std 802.3-2022, Table 33-11 specifies ILIM parameter ranges: + * - For Type 1 PSE operation (PD Classes 0-3): + * The minimum ILIM is 0.400A. This driver uses 425mA. This value fits + * within typical Type 1 ILIM specifications (e.g., 0.400A min to + * around 0.440A-0.500A max for the programmed steady-state limit). + * + * - For Type 2 PSE operation (typically PD Class 4): + * The minimum ILIM is 1.14 * ICable (or ~1.05 * IPort_max from other + * interpretations, e.g., ~0.630A to ~0.684A). This driver uses 850mA. + * This value meets the minimum requirement and is a supported operational + * current limit for high power modes in the LTC4266. + * + * The overall PSE current output must not exceed the time-dependent PSE + * upperbound template, IPSEUT(t), described in IEEE Std 802.3-2022, + * Equation (33-6). The programmed ILIM values (425mA/850mA) serve as the + * long-term current limit (Ilimmin segment of IPSEUT(t)) and are well + * within the higher short-term current allowances of that template (e.g.,= 1.75A). + * + * Returns: 0 on success or a negative errno. + */ +static int ltc4266_port_set_ilim(struct ltc4266_port *port, int class) +{ + bool rsense_250 =3D port->rsense =3D=3D LTC4266_RSENSE_250; + u8 ilim; + + if (class > 4 || class < 0) + return -EINVAL; + + if (class < 4) + ilim =3D rsense_250 ? LTC4266_ILIM_TYPE1_RSENSE_025 : + LTC4266_ILIM_TYPE1_RSENSE_050; + else + ilim =3D rsense_250 ? LTC4266_ILIM_TYPE2_RSENSE_025 : + LTC4266_ILIM_TYPE2_RSENSE_050; + + return regmap_write(port->ltc4266->regmap, + LTC4266_REG_ILIM(port->chan), ilim); +} + +static int ltc4266_port_set_icut(struct ltc4266_port *port, int icut) +{ + u8 val; + + if (icut > LTC4266_ICUT_MAX_MA) + return -ERANGE; + + val =3D min(DIV_ROUND_UP(icut * 1000, LTC4266_ICUT_STEP_UA), + LTC4266_ICUT_MAX_STEPS) & LTC4266_ICUT_MASK; + + if (port->rsense =3D=3D LTC4266_RSENSE_250) + val |=3D LTC4266_ICUT_RSENSE_025_OHM | LTC4266_ICUT_RANGE; + + return regmap_write(port->ltc4266->regmap, + LTC4266_REG_ICUT_HP(port->chan), val); +} + +/** + * ltc4266_pw_limit_to_icut - Convert an admin power limit to an I_CUT thr= eshold + * @port: the port to convert for + * @max_mw: the power limit in mW + * @class: the detected PD class (0-4) + * + * The LTC4266 only enforces a current threshold, so a power limit has to = be + * divided by the port voltage. + * + * Lowering I_CUT below the class threshold is legitimate: IEEE 802.3-2022 + * 33.2.7.10 defines P_Class as either the class power or "PSE allocated p= ower + * ... added to the channel power loss", and an administrative limit is th= at + * allocated power. + * + * Raising it above the class threshold is not. Table 33-11 item 7 bounds = I_CUT + * at I_LIM, and ltc4266_port_set_ilim() picks I_LIM from the class: 425 m= A for + * Type 1, 850 mA for Type 2. An administrative limit of LTC4266_PW_LIMIT_= MAX + * asks for 600 mA at the nominal port voltage, which would exceed I_LIM o= n a + * Type 1 class. Capping at the class threshold from datasheet Table 2 kee= ps + * I_CUT under I_LIM for every class; the LTC4266_ICUT_MAX_MA check in + * ltc4266_port_set_icut() only covers Type 2. + * + * Return: the threshold in mA, or a negative errno if the voltage read fa= iled. + */ +static int ltc4266_pw_limit_to_icut(struct ltc4266_port *port, int max_mw, + int class) +{ + int uv =3D ltc4266_port_voltage_uv(port); + int icut_mA; + + if (uv < 0) + return uv; + + /* 30000 mW x 1000000 overflows an int */ + icut_mA =3D DIV_ROUND_UP_ULL((u64)max_mw * 1000000, uv); + + return min(ltc4266_class_to_icut[class], icut_mA); +} + +static int ltc4266_port_delivering(struct ltc4266_port *port) +{ + unsigned int result; + int ret; + + ret =3D regmap_read(port->ltc4266->regmap, LTC4266_REG_STATPWR, &result); + if (ret < 0) + return ret; + + return !!((result & LTC4266_STATPWR_PG(port->chan)) && + (result & LTC4266_STATPWR_PE(port->chan))); +} + +static int ltc4266_port_init(struct ltc4266_port *port) +{ + struct ltc4266 *ltc4266 =3D port->ltc4266; + u8 chan =3D port->chan; + u8 tlim_shift; + u8 tlim_mask; + u8 tlim_reg; + int ret; + + /* Reset the port */ + ret =3D regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, BIT(chan)); + if (ret < 0) + return ret; + + /* Set Semi-auto mode */ + ret =3D regmap_update_bits(port->ltc4266->regmap, LTC4266_REG_OPMD, + LTC4266_TWO_BIT_WORD_MASK(port->chan), + LTC4266_TWO_BIT_WORD_OFFSET(LTC4266_OPMD_SEMI, + port->chan)); + if (ret < 0) + return ret; + + /* Enable high power mode on the port (for Type 2 PD support) */ + ret =3D regmap_update_bits(ltc4266->regmap, LTC4266_REG_HPEN, + LTC4266_HPEN(chan), LTC4266_HPEN(chan)); + if (ret < 0) + return ret; + + /* Enable 2-event classification (IEEE 802.3-2022, Clause 33), which the + * datasheet refers to as "Ping-Pong" classification. + */ + ret =3D regmap_update_bits(ltc4266->regmap, LTC4266_REG_HPMD(chan), + LTC4266_HPMD_PONGEN, LTC4266_HPMD_PONGEN); + if (ret < 0) + return ret; + + if (chan <=3D 1) + tlim_reg =3D LTC4266_REG_TLIM12; + else + tlim_reg =3D LTC4266_REG_TLIM34; + + /* Each tlim register packs two ports: the even port in the low nibble + * and the odd port in the high nibble. Shift both the value and the + * mask into the correct nibble. + */ + if (chan & BIT(0)) + tlim_shift =3D 4; + else + tlim_shift =3D 0; + + tlim_mask =3D GENMASK(3, 0) << tlim_shift; + + ret =3D regmap_update_bits(ltc4266->regmap, tlim_reg, + tlim_mask, LTC4266_TLIM_VALUE << tlim_shift); + if (ret < 0) + return ret; + + /* Enable disconnect detect. */ + ret =3D regmap_update_bits(ltc4266->regmap, LTC4266_REG_DISENA, + BIT(chan), BIT(chan)); + if (ret < 0) + return ret; + + /* Enable detection (low nibble), classification (high nibble) on the por= t */ + ret =3D regmap_write(ltc4266->regmap, LTC4266_REG_DETPB, + LTC4266_DETPB_CLASS_ENABLE(chan) | + LTC4266_DETPB_DETECT_ENABLE(chan)); + + if (ret < 0) + return ret; + + dev_dbg(ltc4266->dev, "Channel %d has been initialized\n", chan); + return 0; +} + +/* Read the port's classification result and return a class 0-4 or an erro= r if + * the result isn't valid. + */ +static int ltc4266_port_get_class(struct ltc4266_port *port) +{ + struct ltc4266 *ltc4266 =3D port->ltc4266; + unsigned int val; + int ret; + + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_STAT(port->chan), &val); + if (ret < 0) { + dev_warn(ltc4266->dev, "Failed to read status register, err=3D%d\n", ret= ); + return ret; + } + + /* Can't have a valid classification result if we've not yet had a good + * detection result. + */ + if (LTC4266_PORT_DETECT(val) !=3D LTC4266_DETECT_GOOD) + return -EINVAL; + + ret =3D ltc4266_class_map[LTC4266_PORT_CLASS(val)]; + return ret; +} + +/* Maximum power the classified PD is allowed. It does not depend on the p= ort + * being powered, but it does depend on the port having a PD attached and = being + * enabled to the point of running classification and detection cycles. + */ +static int ltc4266_port_max_pw(struct ltc4266_port *port) +{ + int class =3D ltc4266_port_get_class(port); + + if (class < 0) + return class; + + return ltc4266_class_pw[class]; +} + +static int ltc4266_pi_get_pw_status(struct pse_controller_dev *pcdev, int = id, + struct pse_pw_status *pw_status) +{ + struct ltc4266_port *port =3D ltc4266_pi_port(pcdev, id); + int ret; + + ret =3D ltc4266_port_delivering(port); + if (ret < 0) + return ret; + + if (ret) + pw_status->c33_pw_status =3D ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + else + pw_status->c33_pw_status =3D ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; + + return 0; +} + +/* With the static budget evaluation strategy the PSE core calls this only + * after a PD has been classified and the power budget has been allocated. + * Program the current limit for the classified PD, reduced if an admin po= wer + * limit asks for less than the class is entitled to, and apply power. + */ +static int ltc4266_pi_enable(struct pse_controller_dev *pcdev, int id) +{ + struct ltc4266_port *port =3D ltc4266_pi_port(pcdev, id); + int class, icut, ret; + + class =3D ltc4266_port_get_class(port); + if (class < 0) + return class; + + ret =3D ltc4266_port_set_ilim(port, class); + if (ret < 0) + return ret; + + /* Derive the threshold from the admin limit against the class in front of + * us now, since a different PD may have been plugged in since the limit + * was set. + */ + icut =3D ltc4266_pw_limit_to_icut(port, port->pw_limit, class); + if (icut < 0) + return icut; + + ret =3D ltc4266_port_set_icut(port, icut); + if (ret < 0) + return ret; + + /* Apply power to the port. */ + return regmap_write(port->ltc4266->regmap, LTC4266_REG_PWRPB, + BIT(port->chan)); +} + +static int ltc4266_pi_disable(struct pse_controller_dev *pcdev, int id) +{ + struct ltc4266_port *port =3D ltc4266_pi_port(pcdev, id); + + /* Resetting the port (RSTPB, issued at the start of ltc4266_port_init) + * removes power, disables detection and classification, and clears the + * port's status register. Re-init the port so that detection and + * classification can happen again. + */ + return ltc4266_port_init(port); +} + +static int ltc4266_pi_get_voltage(struct pse_controller_dev *pcdev, int id) +{ + return ltc4266_port_voltage_uv(ltc4266_pi_port(pcdev, id)); +} + +static int ltc4266_pi_get_admin_state(struct pse_controller_dev *pcdev, in= t id, + struct pse_admin_state *admin_state) +{ + struct ltc4266_port *port =3D ltc4266_pi_port(pcdev, id); + unsigned int val; + int ret; + + /* The PSE core uses this as the hardware admin state: whether power has + * been commanded on, not whether it has finished coming up. + * pse_pi_is_hw_enabled() decides from it which software-enabled PIs + * still need power delivery attempted, so report the power-enable + * nibble (peN), which the controller sets as soon as it applies power to + * the port. The power-good nibble only sets once OUT has pulled down to + * VEE, up to a tSTART later, and a port reported as not enabled for that + * whole ramp is one the core will allocate power domain budget for a + * second time. Actual delivery is reported by pi_get_pw_status(). + */ + ret =3D regmap_read(port->ltc4266->regmap, LTC4266_REG_STATPWR, &val); + if (ret < 0) + return ret; + + if (val & LTC4266_STATPWR_PE(port->chan)) + admin_state->c33_admin_state =3D + ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else + admin_state->c33_admin_state =3D + ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + + return 0; +} + +static int ltc4266_pi_get_pw_class(struct pse_controller_dev *pcdev, int i= d) +{ + int ret =3D ltc4266_port_get_class(ltc4266_pi_port(pcdev, id)); + + /* ltc4266_port_get_class will return either the class, or an errno. + * Returning an errno from this function will mean the ethtool command + * will abort with the error and not emit later useful information. + * Additionally, returning 0 to indicate no classification signature + * will cause the ethtool output to omit the "Power Class" attribute + * entirely. Since the "Power Class" is expected to be returned here, + * and class 0 and class 3 are equivalent in terms of power allocation, + * we'll return 3. + */ + + if (ret =3D=3D 0) + ret =3D 3; + if (ret < 0) + ret =3D 0; + return ret; +} + +/* Get the power requested by the PD before enabling the port: its + * classification power. + */ +static int ltc4266_pi_get_pw_req(struct pse_controller_dev *pcdev, int id) +{ + return ltc4266_port_max_pw(ltc4266_pi_port(pcdev, id)); +} + +static int ltc4266_pi_get_actual_pw(struct pse_controller_dev *pcdev, int = id) +{ + struct ltc4266_port *port =3D ltc4266_pi_port(pcdev, id); + int uA, uV; + + uA =3D ltc4266_read_iv(port, LTC4266_READ_CURRENT); + if (uA < 0) + return uA; + + uV =3D ltc4266_read_iv(port, LTC4266_READ_VOLTAGE); + if (uV < 0) + return uV; + + /* mW =3D uA * uV / 1000000000 */ + return DIV_ROUND_CLOSEST_ULL((u64)uA * uV, 1000000000); +} + +/* The range of administrative power limits this controller supports. It d= oes + * not depend on the detected class: the class only bounds the I_CUT thres= hold + * that ltc4266_pw_limit_to_icut() programs. + */ +static int ltc4266_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev= , int id, + struct pse_pw_limit_ranges *pw_limit_ranges) +{ + struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; + + c33_pw_limit_ranges =3D kzalloc_obj(*c33_pw_limit_ranges); + if (!c33_pw_limit_ranges) + return -ENOMEM; + + c33_pw_limit_ranges[0].min =3D LTC4266_PW_LIMIT_MIN; + c33_pw_limit_ranges[0].max =3D LTC4266_PW_LIMIT_MAX; + + pw_limit_ranges->c33_pw_limit_ranges =3D c33_pw_limit_ranges; + + /* Return the number of ranges */ + return 1; +} + +/* Store the administrative power limit. The limit is independent of any P= D in + * front of us, so accept it whether or not the port has classified anythi= ng; + * program I_CUT immediately when it has, so a change on a live port takes + * effect, and leave it to ltc4266_pi_enable() otherwise. + */ +static int ltc4266_pi_set_pw_limit(struct pse_controller_dev *pcdev, + int id, int max_mw) +{ + struct ltc4266_port *port =3D ltc4266_pi_port(pcdev, id); + int class; + int icut; + int ret; + + if (max_mw < LTC4266_PW_LIMIT_MIN || max_mw > LTC4266_PW_LIMIT_MAX) { + dev_err(port->ltc4266->dev, "power limit %d is out of range [%d, %d]\n", + max_mw, LTC4266_PW_LIMIT_MIN, LTC4266_PW_LIMIT_MAX); + return -ERANGE; + } + + class =3D ltc4266_port_get_class(port); + if (class >=3D 0) { + icut =3D ltc4266_pw_limit_to_icut(port, max_mw, class); + if (icut < 0) + return icut; + + ret =3D ltc4266_port_set_icut(port, icut); + if (ret < 0) + return ret; + } + + port->pw_limit =3D max_mw; + + return 0; +} + +static int ltc4266_pi_get_pw_limit(struct pse_controller_dev *pcdev, int i= d) +{ + return ltc4266_pi_port(pcdev, id)->pw_limit; +} + +/* Description of one delivery channel parsed out of the "channels" node. = Used + * locally only during ltc4266_setup_pi_matrix() + */ +struct ltc4266_chan_desc { + struct device_node *np; + enum sense_resistor rsense; +}; + +static int ltc4266_get_of_channels(struct ltc4266 *ltc4266, + struct ltc4266_chan_desc *chans) +{ + struct device_node *channels_node __free(device_node) =3D + of_get_child_by_name(ltc4266->np, "channels"); + u32 chan_id, sense; + int ret; + + if (!channels_node) + return dev_err_probe(ltc4266->dev, -EINVAL, + "missing \"channels\" node\n"); + + for_each_child_of_node_scoped(channels_node, chan_node) { + if (!of_node_name_eq(chan_node, "channel")) + continue; + + ret =3D of_property_read_u32(chan_node, "reg", &chan_id); + if (ret) + return dev_err_probe(ltc4266->dev, ret, + "missing reg property in node %pOF\n", + chan_node); + + if (chan_id >=3D LTC4266_MAX_PORTS) + return dev_err_probe(ltc4266->dev, -EINVAL, + "channel id %u is out of range in node %pOF\n", + chan_id, chan_node); + + if (chans[chan_id].np) + return dev_err_probe(ltc4266->dev, -EINVAL, + "channel id %u is already used, please check the reg property in= node %pOF\n", + chan_id, chan_node); + + ret =3D of_property_read_u32(chan_node, "sense-resistor-micro-ohms", &se= nse); + if (ret) + return dev_err_probe(ltc4266->dev, ret, + "missing sense-resistor-micro-ohms property in node %pOF\n", + chan_node); + + if (sense =3D=3D 250000) + chans[chan_id].rsense =3D LTC4266_RSENSE_250; + else if (sense =3D=3D 500000) + chans[chan_id].rsense =3D LTC4266_RSENSE_500; + else + return dev_err_probe(ltc4266->dev, -EINVAL, + "invalid sense resistor value %u in node %pOF\n", + sense, chan_node); + + chans[chan_id].np =3D of_node_get(chan_node); + } + + return 0; +} + +/* Find the channel a PI pairset phandle points at. */ +static int ltc4266_match_channel(const struct pse_pi_pairset *pairset, + struct ltc4266_chan_desc *chans) +{ + int i; + + for (i =3D 0; i < LTC4266_MAX_PORTS; i++) + if (pairset->np =3D=3D chans[i].np) + return i; + + return -ENODEV; +} + +/* + * Nominal voltage of the rail feeding a PSE PI, taken from its "vpwr-supp= ly". + * This is intended to work around the limitation on the LTC4266 where the= port + * voltage is not valid until _after_ the port is powered. Since the PSE = core + * expects to read the port voltage when the port is not powered we need + * something non-zero to return to the core. + * + * In the event the regulator isn't present, or if the regulator doesn't h= ave a + * nominal voltage available, we'll fall back and use 50V, which is the mi= nimum + * allowed for a Type 2 PSE. + * + * Finally, we read this regulator voltage here instead of during + * ltc4266_pi_get_voltage to avoid deadlock. + * + * Return: the nominal voltage in uV, or LTC4266_VPORT_NOMINAL_UV if the PI + * describes no supply, its supply has not registered yet, or its voltage + * cannot be determined. + */ +static int ltc4266_pi_nominal_uv(struct ltc4266 *ltc4266, struct device_no= de *np) +{ + struct regulator *vpwr; + int uv; + + vpwr =3D of_regulator_get_optional(ltc4266->dev, np, "vpwr"); + if (IS_ERR(vpwr)) { + /* -ENODEV means the PI describes no vpwr-supply at all, which + * is the case the fallback exists for. -EPROBE_DEFER means the + * rail _is_ described but has not registered yet, so the + * fallback is wrong for it. Asking for a probe retry is not an + * option from here: we run from setup_pi_matrix(), and + * pse_controller_register() unwinds neither its notification + * fifo nor its pse_pi array when that fails, so every retry + * would leak. Warn instead so the assumed voltage is visible. + */ + if (PTR_ERR(vpwr) =3D=3D -EPROBE_DEFER) + dev_warn(ltc4266->dev, + "%pOF: vpwr-supply is not registered yet, assuming %d uV\n", + np, LTC4266_VPORT_NOMINAL_UV); + + return LTC4266_VPORT_NOMINAL_UV; + } + + uv =3D regulator_get_voltage(vpwr); + regulator_put(vpwr); + + if (uv <=3D 0) + return LTC4266_VPORT_NOMINAL_UV; + + return uv; +} + +/* Build a port context for every PSE PI described in the device tree, bou= nd to + * the channel its pairset references. A PI with no node is left NULL; the= PSE + * core does not register a regulator for it and so never calls back for i= t. + */ +static int ltc4266_map_pis(struct ltc4266 *ltc4266, + struct ltc4266_chan_desc *chans) +{ + struct pse_controller_dev *pcdev =3D <c4266->pcdev; + struct ltc4266_port *port; + int i, j, chan, uv; + + for (i =3D 0; i < LTC4266_MAX_PORTS; i++) { + struct pse_pi *pi =3D &pcdev->pi[i]; + + if (!pi->np) + continue; + + if (!pi->pairset[0].np) + return dev_err_probe(ltc4266->dev, -EINVAL, + "%pOF has no pairsets\n", pi->np); + + /* The LTC4266 delivers over a single pairset per channel, so + * there is no 4-pair mode to spread a PI over two channels. + */ + if (pi->pairset[1].np) + return dev_err_probe(ltc4266->dev, -EOPNOTSUPP, + "%pOF: 4-pair PSE PIs are not supported\n", + pi->np); + + chan =3D ltc4266_match_channel(&pi->pairset[0], chans); + if (chan < 0) + return dev_err_probe(ltc4266->dev, chan, + "%pOF: pairset %pOF is not a channel of this controller\n", + pi->np, pi->pairset[0].np); + + for (j =3D 0; j < i; j++) + if (ltc4266->ports[j] && ltc4266->ports[j]->chan =3D=3D chan) + return dev_err_probe(ltc4266->dev, -EINVAL, + "%pOF: channel %d is already used by %pOF\n", + pi->np, chan, pcdev->pi[j].np); + + uv =3D ltc4266_pi_nominal_uv(ltc4266, pi->np); + + port =3D devm_kzalloc(ltc4266->dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->ltc4266 =3D ltc4266; + port->chan =3D chan; + port->rsense =3D chans[chan].rsense; + port->vpwr_uv =3D uv; + port->pw_limit =3D LTC4266_PW_LIMIT_MAX; + ltc4266->ports[i] =3D port; + + dev_dbg(ltc4266->dev, "PI %d is backed by channel %d, nominal %d uV\n", + i, chan, uv); + } + + return 0; +} + +static int ltc4266_setup_pi_matrix(struct pse_controller_dev *pcdev) +{ + struct ltc4266 *ltc4266 =3D container_of(pcdev, struct ltc4266, pcdev); + struct ltc4266_chan_desc chans[LTC4266_MAX_PORTS] =3D { }; + int i, ret; + + if (pcdev->no_of_pse_pi) + return dev_err_probe(ltc4266->dev, -EINVAL, + "a \"pse-pis\" node is required\n"); + + ret =3D ltc4266_get_of_channels(ltc4266, chans); + if (!ret) + ret =3D ltc4266_map_pis(ltc4266, chans); + + for (i =3D 0; i < LTC4266_MAX_PORTS; i++) + of_node_put(chans[i].np); + + if (ret) + return ret; + + for (i =3D 0; i < LTC4266_MAX_PORTS; i++) { + if (!ltc4266->ports[i]) + continue; + + ret =3D ltc4266_port_init(ltc4266->ports[i]); + if (ret < 0) + return dev_err_probe(ltc4266->dev, ret, + "Failed to initialize PI %d\n", i); + } + + return 0; +} + +static const struct pse_controller_ops ltc4266_ops =3D { + .setup_pi_matrix =3D ltc4266_setup_pi_matrix, + .pi_get_admin_state =3D ltc4266_pi_get_admin_state, + .pi_get_pw_status =3D ltc4266_pi_get_pw_status, + .pi_get_pw_class =3D ltc4266_pi_get_pw_class, + .pi_get_actual_pw =3D ltc4266_pi_get_actual_pw, + .pi_enable =3D ltc4266_pi_enable, + .pi_disable =3D ltc4266_pi_disable, + .pi_get_voltage =3D ltc4266_pi_get_voltage, + .pi_get_pw_limit =3D ltc4266_pi_get_pw_limit, + .pi_set_pw_limit =3D ltc4266_pi_set_pw_limit, + .pi_get_pw_limit_ranges =3D ltc4266_pi_get_pw_limit_ranges, + .pi_get_pw_req =3D ltc4266_pi_get_pw_req, +}; + +#define LTC4266_INTERRUPT_SOURCES (LTC4266_INT_TSTART | LTC4266_INT_TCUT |= \ + LTC4266_INT_CLASS | LTC4266_INT_DETECT | \ + LTC4266_INT_DIS | LTC4266_INT_PWRGD) + +static int ltc4266_enable_interrupts(struct ltc4266 *ltc4266) +{ + /* Unmask interrupts */ + return regmap_write(ltc4266->regmap, LTC4266_REG_INTMASK, + LTC4266_INTERRUPT_SOURCES); +} + +static int ltc4266_disable_interrupts(struct ltc4266 *ltc4266) +{ + int ret; + + ret =3D regmap_write(ltc4266->regmap, LTC4266_REG_INTMASK, 0x00); + if (ret < 0) + return ret; + + /* Reset the (SMBus Alert) interrupt pin */ + return regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_PIN= CLR); +} + +static int ltc4266_map_event(int irq, struct pse_controller_dev *pcdev, + unsigned long *notifs, unsigned long *notifs_mask) +{ + struct ltc4266 *ltc4266 =3D container_of(pcdev, struct ltc4266, pcdev); + unsigned int detevn =3D 0, fltevn =3D 0, tsevn =3D 0, pwrevn =3D 0; + unsigned int statpwr =3D 0; + unsigned int intstat; + int ret; + int i; + + ret =3D ltc4266_disable_interrupts(ltc4266); + if (ret < 0) + goto err; + + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_INTSTAT, &intstat); + if (ret < 0) + goto err; + + if (!intstat) + goto done; + + if (intstat & LTC4266_INT_PWRGD) { + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_PWREVN_COR, &pwrevn); + if (ret < 0) { + dev_err(<c4266->client->dev, "Failed to read pwrevn, err=3D%d\n", ret= ); + goto err; + } + + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_STATPWR, &statpwr); + if (ret < 0) { + dev_err(<c4266->client->dev, "Failed to read statpwr, err=3D%d\n", re= t); + goto err; + } + } + + if (intstat & (LTC4266_INT_DIS | LTC4266_INT_TCUT)) { + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_FLTEVN_COR, &fltevn); + if (ret < 0) { + dev_err(<c4266->client->dev, "Failed to read fltevn err=3D%d\n", ret); + goto err; + } + } + + if (intstat & (LTC4266_INT_TSTART | LTC4266_INT_TCUT)) { + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_TSEVN_COR, &tsevn); + if (ret < 0) { + dev_err(<c4266->client->dev, "Failed to read tsevn, err=3D%d\n", ret); + goto err; + } + } + + if (intstat & (LTC4266_INT_CLASS | LTC4266_INT_DETECT)) { + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_DETEVN_COR, &detevn); + if (ret < 0) { + dev_err(<c4266->client->dev, "Failed to read detevn, err=3D%d\n", ret= ); + goto err; + } + } + + /* The event registers are indexed by delivery channel while notifs[] is + * indexed by PSE PI id, so walk the PIs and use each one's channel to + * select the event bits. + */ + for (i =3D 0; i < LTC4266_MAX_PORTS; i++) { + struct ltc4266_port *port =3D ltc4266->ports[i]; + unsigned int fault; + bool pg_lost; + u8 chan; + + if (!port) + continue; + + chan =3D port->chan; + + fault =3D (tsevn | fltevn) & + (LTC4266_EVN_HI(chan) | LTC4266_EVN_LO(chan)); + + /* The controller also drops a port without reporting any + * per-port fault event, for instance on an over-temperature + * shutdown or when it finds a failed external MOSFET. Both of + * those clear the port's detection and classification enables, + * so the port stays dark until it is re-initialised. A power + * good change that leaves statpwr[pg] clear catches them, and + * ignores the rising edge of a port that has just been powered + * on. + */ + pg_lost =3D (pwrevn & LTC4266_EVN_HI(chan)) && + !(statpwr & LTC4266_STATPWR_PG(chan)); + + if (fault || pg_lost) { + /* The port has gone down; if we see this, then we don't + * care if any of the remaining events are set. If the + * device did disconnect briefly, it'll redetect and + * reclassify accordingly + */ + notifs[i] |=3D ETHTOOL_C33_PSE_EVENT_DISCONNECTION; + *notifs_mask |=3D BIT(i); + + /* Report over-current if the port went down for any + * overcurrent reason: tSTART (tsevn low nibble, startup + * inrush), tLIM (tsevn high nibble, current-limit + * timeout) or tCUT (fltevn low nibble, I_CUT timeout). + */ + if ((tsevn & (LTC4266_EVN_LO(chan) | LTC4266_EVN_HI(chan))) || + (fltevn & LTC4266_EVN_LO(chan))) + notifs[i] |=3D ETHTOOL_PSE_EVENT_OVER_CURRENT; + + dev_dbg(<c4266->client->dev, + "tsevn=3D0x%02X fltevn=3D0x%02X pwrevn=3D0x%02X statpwr=3D0x%02X\n", + tsevn, fltevn, pwrevn, statpwr); + continue; + } + if (detevn & LTC4266_EVN_LO(chan)) { + /* Read the detect result, and if it isn't detect good, + * call it a disconnect + */ + unsigned int detval; + + ret =3D regmap_read(ltc4266->regmap, LTC4266_REG_STAT(chan), &detval); + if (ret < 0) { + dev_warn(ltc4266->dev, "Failed to read status register, err=3D%d\n", + ret); + continue; + } + + if (LTC4266_PORT_DETECT(detval) !=3D LTC4266_DETECT_GOOD) { + notifs[i] |=3D ETHTOOL_C33_PSE_EVENT_DISCONNECTION; + *notifs_mask |=3D BIT(i); + continue; + } + } + + if (detevn & LTC4266_EVN_HI(chan)) { + int class =3D ltc4266_port_get_class(port); + + if (class >=3D 0) { + notifs[i] |=3D ETHTOOL_C33_PSE_EVENT_CLASSIFICATION; + *notifs_mask |=3D BIT(i); + } + } + } + +done: + return ltc4266_enable_interrupts(ltc4266); + +err: + /* (Attempt to) clear any remaining event registers that we might've + * missed in the event a previous read has failed. + */ + ret =3D regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_IN= TCLR); + if (ret) + dev_warn(<c4266->client->dev, "Failed to clear pending interrupts, err= =3D%d\n", + ret); + + return ltc4266_enable_interrupts(ltc4266); +} + +static const struct regmap_config ltc4266_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D 0x5F, +}; + +static void ltc4266_teardown(void *data) +{ + struct ltc4266 *ltc4266 =3D data; + + ltc4266_disable_interrupts(ltc4266); + + /* Prevent the chip from asserting interrupts */ + regmap_update_bits(ltc4266->regmap, LTC4266_REG_MCONF, + LTC4266_MCONF_INTERRUPT_ENABLE, 0); + + /* Reset all the ports and do not re-init: a port reset removes power and + * clears the port's detection and classification enables, leaving it in + * semi-auto mode, which never powers a port without a host request. + */ + regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_RSTPORTS); +} + +static int ltc4266_probe(struct i2c_client *client) +{ + struct ltc4266 *ltc4266; + struct regmap *regmap; + unsigned int id_reg; + int ret; + + struct pse_irq_desc irq_desc =3D { + .name =3D "ltc4266-irq", + .map_event =3D ltc4266_map_event, + }; + + /* We need IRQ for static power budgeting and if don't have it, fail + * probe early + */ + if (!client->irq) + return dev_err_probe(&client->dev, -EINVAL, + "Interrupt is required for power budget management\n"); + + regmap =3D devm_regmap_init_i2c(client, <c4266_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "Failed to allocate regmap\n"); + + /* Confirm we are talking to an LTC4266: the id register (0x1B) should + * read back its documented reset value of 0x64. + */ + ret =3D regmap_read(regmap, LTC4266_REG_ID, &id_reg); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Failed to read ID register\n"); + + if (id_reg !=3D LTC4266_ID) + return dev_err_probe(&client->dev, -ENODEV, + "Expected an ID of 0x64, saw 0x%02X\n", id_reg); + + /* Reset the chip */ + ret =3D regmap_write(regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_INTCLR | LT= C4266_RSTPB_RSTALL); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Failed to reset\n"); + + /* LTC4266 requires approximately 10 ms after reset to be stable; if it + * isn't, then there is typically an undervoltage lockout/something prett= y bad + * going on. We give it 50 ms here so we don't need to poll the chip and = use I2C bandwidth + */ + msleep(50); + + /* Let's make sure the chip came out of reset (if not, the chip is probab= ly + * either (no longer?) present, in thermal shutdown, or watchdogged....ei= ther + * way, there's nothing we can do in software to fix it) + */ + ret =3D regmap_read(regmap, LTC4266_REG_ID, &id_reg); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to re-read ID register after reset\n"); + + if (id_reg !=3D LTC4266_ID) + return dev_err_probe(&client->dev, -ENODEV, + "Failed to re-read device ID after reset 0x%02X\n", + id_reg); + + ltc4266 =3D devm_kzalloc(&client->dev, sizeof(struct ltc4266), GFP_KERNEL= ); + if (!ltc4266) + return -ENOMEM; + + ltc4266->client =3D client; + ltc4266->regmap =3D regmap; + ltc4266->np =3D client->dev.of_node; + ltc4266->dev =3D &client->dev; + + /* After reset, the LTC4266 will interrupt with a (single) supply fault. + * Clear it here and discard the result + */ + regmap_read(ltc4266->regmap, LTC4266_REG_SUPEVN_COR, &id_reg); + + ret =3D ltc4266_disable_interrupts(ltc4266); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to disable interrupts\n"); + + /* Registered before the controller and the IRQ so devres ordering runs + * it after free_irq() and pse_controller_unregister(), and so a failed + * probe cannot leave ports running detection with no driver bound. + */ + ret =3D devm_add_action_or_reset(&client->dev, ltc4266_teardown, ltc4266); + if (ret) + return ret; + + ltc4266->pcdev.owner =3D THIS_MODULE; + ltc4266->pcdev.ops =3D <c4266_ops; + ltc4266->pcdev.dev =3D &client->dev; + ltc4266->pcdev.types =3D ETHTOOL_PSE_C33; + ltc4266->pcdev.nr_lines =3D LTC4266_MAX_PORTS; + ltc4266->pcdev.supp_budget_eval_strategies =3D PSE_BUDGET_EVAL_STRAT_STAT= IC; + + ret =3D devm_pse_controller_register(ltc4266->dev, <c4266->pcdev); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register PSE controller\n"); + + /* Enable the interrupt pin, and only report detect events on + * change (detchg) so idle ports continuously re-running + * detection in semi-auto mode don't flood the host with a + * detect event every cycle. + */ + ret =3D regmap_update_bits(ltc4266->regmap, LTC4266_REG_MCONF, + LTC4266_MCONF_INTERRUPT_ENABLE | LTC4266_MCONF_DETCHG, + LTC4266_MCONF_INTERRUPT_ENABLE | LTC4266_MCONF_DETCHG); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to configure interrupts\n"); + + ret =3D devm_pse_irq_helper(<c4266->pcdev, client->irq, + 0, &irq_desc); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register PSE IRQ\n"); + + /* Unmask the chip interrupt sources now the handler is ready. */ + ret =3D ltc4266_enable_interrupts(ltc4266); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to enable interrupts\n"); + + return 0; +} + +static const struct i2c_device_id ltc4266_id[] =3D { + {.name =3D "ltc4266"}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4266_id); + +static const struct of_device_id ltc4266_of_match[] =3D { + { .compatible =3D "lltc,ltc4266" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc4266_of_match); + +static struct i2c_driver ltc4266_driver =3D { + .driver =3D { + .name =3D "ltc4266", + .of_match_table =3D ltc4266_of_match, + }, + .probe =3D ltc4266_probe, + .id_table =3D ltc4266_id, +}; +module_i2c_driver(ltc4266_driver); + +MODULE_AUTHOR("Kyle Swenson "); +MODULE_DESCRIPTION("LTC4266 PoE PSE Controller Driver"); +MODULE_LICENSE("GPL"); --=20 2.55.0