From nobody Thu Sep 24 13:01:01 2026 Received: from CHN02-BJS-obe.outbound.protection.partner.outlook.cn (mail-bjschn02on2119.outbound.protection.partner.outlook.cn [139.219.17.119]) (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 0876B5221DA; Wed, 23 Sep 2026 13:08:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=139.219.17.119 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790168887; cv=fail; b=t5v414hRFmf+GrFk6Vxi8lYrD+iL2A2AK42KH80yi0flqhXUSzoFDvVZNVPSAR0E9ICLUMR1DE2G8//aRqU/YL/jipD/mhg/jbLx1bjPeY7d6RIW7CY/lT122n86w2XCwHWCUJ2K53BTcN/VvaDt8k3uGs9XxeXR9nGfhrbENQU= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790168887; c=relaxed/simple; bh=yPzMdc5uSNQEZSJNj852M6K1tKqxqfOq1QcjYniR7ZA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: Content-Type:MIME-Version; b=hZTCQcf7uEdJkQxybfskR9vHWP+N0Sw+UPaMmtY2r4oEwgXOS/Nnbdb3KQunFtQ6sYmrMp3uns8G6wWTXyh2I/Wep3RQ141q/4/+Wu+ql2zJUoDU6SxBypmPr2en6J7g42kWdIyh/BzKARumylLc3hvwUbszAQf3XnVT4Ywqx0E= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=starfivetech.com; spf=pass smtp.mailfrom=starfivetech.com; arc=fail smtp.client-ip=139.219.17.119 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=starfivetech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=starfivetech.com ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=oCevYzbM4voOiJ7hMzwN//+wuws5Al3sOpyg4ajDeGUgfg9c08BE6PUTKMs0qgsvJVLmJu5MOYa9sb9vpTpS5w00VLyuJ3MqhfQy9lxvhXgCjk/KX8tw8+VDRGyzdrXKED6hgoDm8x2D62AQWWDTzCeD+mh1creA0teyt3dauMNc3S1i4UPuootwGxjPBMxhqablf/E5JAuMRxkZDcUV8ln69v6NkHsig8EonYAZE7qzSDPQ+kba2FhjpWG/PvBMM+XrzjcFOvAb+QzjDHW8rg+F2vLgwRcsDq0ogpNulY3QMvD1tAqz35G9XUD7sKB2Gha9nkNsB3qNgiV6ObyaOw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; 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=43Crd11IEvXJtYYfC5+sgwMv9lfseyRASTLjKd34rpM=; b=YO9BzW6F7h3Rc9zHiMcAFCM9XSvdcuutjluAyyGdPakV++YA9mcVg9L3o3YXMDvwwFivPs68tnMCSDKcU4XOetW9mjwGvAzr3TeI4YtsMJ5vhmTY6KY7U22OpHm3JUytZ3fFG4z3TiFrwDe+yl/5keuQ4Bkbv8nPvb6zuJ/6/w2L7RpU+XUz8mHaNJewGGEtITJV5adaysMRiaDB3fzS/hi3MYZIVsKYOV65Iv3jWVhIqt1m9V9fuY7pyoC8oLjCiDG2UzWXx13FY1TIIymdgv0F4NiSXhoayHJQxe7QscwPumOdobsAdEl+Hg4fSJ7UlWuLMyR2zfuqwQpaZYa+zQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=starfivetech.com; dmarc=pass action=none header.from=starfivetech.com; dkim=pass header.d=starfivetech.com; arc=none Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=starfivetech.com; Received: from ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn (2406:e500:c550:17::6) by ZQ4PR01MB1315.CHNPR01.prod.partner.outlook.cn (2406:e500:c550:15::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.17; Wed, 23 Sep 2026 13:07:50 +0000 Received: from ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn ([fe80::e7d4:256c:b066:850d]) by ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn ([fe80::e7d4:256c:b066:850d%4]) with mapi id 15.21.0451.014; Wed, 23 Sep 2026 13:07:50 +0000 From: Changhuang Liang To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, Changhuang Liang Subject: [PATCH v5 1/2] dt-bindings: hwmon: Add starfive,jhb100-pwm-fan Date: Wed, 23 Sep 2026 06:06:23 -0700 Message-Id: <20260923130624.239499-2-changhuang.liang@starfivetech.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260923130624.239499-1-changhuang.liang@starfivetech.com> References: <20260923130624.239499-1-changhuang.liang@starfivetech.com> Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: SHXPR01CA0008.CHNPR01.prod.partner.outlook.cn (10.43.110.209) To ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn (10.2.3.102) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: ZQ4PR01MB1202:EE_|ZQ4PR01MB1315:EE_ X-MS-Office365-Filtering-Correlation-Id: 45adf90b-0fee-4c26-fe95-08df1973ab96 X-MS-Exchange-SenderADCheck: 1 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|376014|52116014|23010399003|1800799024|38350700014|3023799007|10067099003|56012099006|22082099003|18002099003; X-Microsoft-Antispam-Message-Info: 4rWyg3Qw74voC7RXYNcs33nBkrKC+TaE0wh/W+0yVTolP3dDRi/xY93sUesNmvg2+RBJXUWQDfLQwNWqnksMQCT3ENy/n+ZoP3+dCxQqg3p6Y7AuuURdBjNVyjmOc8VACCBQRD1esbX7uHN7FLLAy3fX9i6RaGNnt7nPD+7c55tGJEaMtnmXTrBjwdtVodVIC9nWOh7unWvsTwXQlwtpfQKqTUwvNloYgcaWXDRfhMW2rFXZ/K9vvIEQd4pOEey/EEcgOBddoZnP3VlZIEhN7T3+Ad2PuryuL+lI6BnQmH6nPMK1G6+4T/9+sNN4ucAWR+jK11u/j5c3DeyO9QMgL3VJ5yU96ekauWRzSOCD2rT/A4e9yHcXrWpKMbPDgmp+7/Qb4x3FP6gpSdIFmfzqasOMiyo647mJf72Y4C7gHpQC/JPR0sUTT+oxgXmpLD3xepOJ9wr4XoTJou0evqYueRBQp8EMBWLzlnWRR94jzvmqyxcELxFrImBDfwaV3j/5xf+z8dYoVC2LUq9uGkFh5CqLmfKhNnbjzT0EF4teKzr6FiB3S6HToHAXD3O8XbdK X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn;PTR:;CAT:NONE;SFS:(13230040)(366016)(376014)(52116014)(23010399003)(1800799024)(38350700014)(3023799007)(10067099003)(56012099006)(22082099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?7goZVt8E076ApF4pFjEv2lXi37c6ePBa/rnGN9a7p+SFH4lLO7yy2n9MhLlM?= =?us-ascii?Q?zT2UXtuvB2358Fc6PMUZ6kxDIIGl1t4EaS1P4rWIc8hb0XqMdO3yBpDt3Kxl?= =?us-ascii?Q?KcstIb0W4qytJ0IrUaNUcPwrbRft/0B/v3ciwxme7IqRCRZKV8EXZmjH4Sl7?= =?us-ascii?Q?Hkxry9TBwF1UqB3CTcAb2AMrRdaiKAXMm0+/3YSCQVLw7UsytAn7URBWEcIE?= =?us-ascii?Q?pEu4tcflfqY0nGGuz25SN4v4ivQzB1bmOcf+5pUCMjJsDnAS6ykdABbtvZlC?= =?us-ascii?Q?0g4II3DugbrwMqh0vJL3Wt4e/i6itr1IESO040KpfpNdmI9LOqR5pjUSLyzj?= =?us-ascii?Q?KkUKfy49nqlkLbJfpd9zyOmiMfWVVzL7oSf3CCRPwOGb7h9tkolvAZxz8P5D?= =?us-ascii?Q?+bwWLXj0GhiNGwZ8E7GeRKjxbzzmFdZc5Z/8MZtxtaed3DuiXRzbbU0bfiy7?= =?us-ascii?Q?gaKVJMoqF7Uhi1bmvlwUqXq98MomngSZuIGg67hy9QOuCD1a9i3G+s3OPQir?= =?us-ascii?Q?7yl9dHtOaCstaJlppNlyC4QdBmJ7wy2Hr3DD8DDi44V2S+o813C7QP7C/1FB?= =?us-ascii?Q?2KcNhjFUX2+HgvGXMfbUG7nNL6/UDIi5hcasUlS+yHFA+QR7HrHhGtEEaS0j?= =?us-ascii?Q?OW3Bl0LH1iYqj3Wu0THKk+6bqcPVt+fSM9X6YH67/uPCRfH81y6aRfa9Zs9U?= =?us-ascii?Q?OF4pRA04Mmmo6aeYHOfAPQtq9saq/yjiEzyiRWRQD60/pyA9Ri6XTKEKpk+j?= =?us-ascii?Q?9nbmceP7tVzG7IzseLSxToD83Rnn6XL7X6StUGV2ZBeEXpebIoycThWyO/eC?= =?us-ascii?Q?HgTrUFQdNivtiSUvk2MUMIwHqvsDxflb/t/wMDldMsgjJFPE/wY3OFAxn72e?= =?us-ascii?Q?HELzDMFjlpz6ZuS9JP6g0jJlK+4mYzQhemnzMcXQmGcSaHMLJWiT7Z5rU04d?= =?us-ascii?Q?9nDkVEe8IEZ1YxsC0dotxNKs+KE55bMipAW+h8mCJ32Vn3yG1M6Bvifgph0w?= =?us-ascii?Q?bY0Obp8P9n1wrUqB7O25ffb097E2liq9e1gsusNZZNnyMWJwJwfD/xWJb8yc?= =?us-ascii?Q?T4eaGq4YVknCy43toKU8arNvjozXWX2noVZr99vkBrYbxhYDhWYsSJlAeRoA?= =?us-ascii?Q?PDcsfkU+bnyrE4dElim2/8YbRN7M6WRbHNm+g7iPLRMJ4P9rv/aDOXzzavE8?= =?us-ascii?Q?xkEbxXSpZFOZDPTWDbUSWiiQOxmZEUbI/ce5L2ieDmXZYMxuGLb47OewzJtL?= =?us-ascii?Q?GowaGRxL6dvifOWi2BxmeBIRDItsQpoDnnlnsTDQFUwrVfaaL6N1LHbAzeEO?= =?us-ascii?Q?f8p/FpKp24wiBhnBbRWICqRDLipXHvTK0VGKmC6oqk9VIgvkGQ2XEsXpCJSA?= =?us-ascii?Q?7w/QlaCn1CDV7LUpZy3IEp+23VCuIPsoHL7Rlcu4QL916Y5gDYpfqk4efn3e?= =?us-ascii?Q?71GYi8VjJ+CnQqfROpWi3P/XZHRf59XdaLQFZTTfz1KGMoa9vIyBqzTRATeg?= =?us-ascii?Q?V1Nz0j/v9Gg2KPt4AhkE8C+BXv2t10yZsllTSW7vOPgbYlR66qqjsFmo4ljh?= =?us-ascii?Q?UgyBbIRj85Tfp4OiAdbXdjBDyGCFivkhxWHZiuxQE6EtQ7lqyPcln6cnom0Z?= =?us-ascii?Q?iljiMVftfcODBVf0061XqsXtzXLhP4EIv0T8gTAac4RCmzQ4l+dVeqz3ngUT?= =?us-ascii?Q?2+4Li3jZpe048FtXavRWSrlk68rn1XFSJ88+4GZ2tQvdJ+CbCvPLo9cZ2Kd/?= =?us-ascii?Q?on8bpTxeX1qgkRMrUZpAU8S1zKTTzNs6Mrak+VO5+NC2ufs/X1E9?= X-OriginatorOrg: starfivetech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 45adf90b-0fee-4c26-fe95-08df1973ab96 X-MS-Exchange-CrossTenant-AuthSource: ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 23 Sep 2026 13:07:50.1853 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 06fe3fa3-1221-43d3-861b-5a4ee687a85c X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 6Op9NU4tl0An94TKTPDswKKCmhL8pscYK/5PmvMMDQ2K8cRMPhs4Ml1RdZh/0sdY/Scl6s2TcsbTesrlvOQtg6pbsyolGVuqOa3zzbq3cod9ye/QKDQPxXCf5890aQFG X-MS-Exchange-Transport-CrossTenantHeadersStamped: ZQ4PR01MB1315 Content-Type: text/plain; charset="utf-8" Add a binding for the StarFive JHB100 PWM fan controller. The controller has a hardware tachometer block with 16 tachometer inputs, while fan speed is controlled through 8 PWM channels supplied by an external PWM controller. One PWM channel can drive up to two fans, so each fan-N child node references one PWM channel and lists the tachometer inputs wired to the fans it drives. Signed-off-by: Changhuang Liang Reviewed-by: Conor Dooley --- .../hwmon/starfive,jhb100-pwm-fan.yaml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/starfive,jhb100= -pwm-fan.yaml diff --git a/Documentation/devicetree/bindings/hwmon/starfive,jhb100-pwm-fa= n.yaml b/Documentation/devicetree/bindings/hwmon/starfive,jhb100-pwm-fan.ya= ml new file mode 100644 index 000000000000..b05789867c0e --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/starfive,jhb100-pwm-fan.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/starfive,jhb100-pwm-fan.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: StarFive JHB100 PWM Fan Controller + +maintainers: + - Changhuang Liang + +description: + The StarFive JHB100 fan controller uses a hardware tachometer block with + 16 tachometer inputs to measure fan speed. Fan speed is controlled throu= gh + 8 PWM channels supplied by an external PWM controller, and one PWM chann= el + can drive up to two fans. Each fan child node references the PWM channel + driving the fan(s) and lists the tachometer inputs wired to them. + +properties: + compatible: + const: starfive,jhb100-pwm-fan + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + interrupts: + maxItems: 1 + +patternProperties: + "^fan-[0-7]$": + $ref: fan-common.yaml# + unevaluatedProperties: false + + properties: + pulses-per-revolution: + minimum: 1 + default: 2 + + tach-ch: + description: + The tachometer input(s) wired to the fan(s) driven by the PWM + channel. One PWM channel can drive up to two fans. + minItems: 1 + maxItems: 2 + items: + maximum: 15 + + required: + - pwms + - tach-ch + +required: + - compatible + - reg + - clocks + - resets + - interrupts + +additionalProperties: false + +examples: + - | + fan-controller@11be0000 { + compatible =3D "starfive,jhb100-pwm-fan"; + reg =3D <0x11be0000 0x400>; + interrupts =3D <128>; + clocks =3D <&per2crg 33>; + resets =3D <&per2crg 5>; + + fan-0 { + pwms =3D <&pwm0 0 40000 0>; + tach-ch =3D /bits/ 8 <0x0 0x8>; + pulses-per-revolution =3D <2>; + }; + + fan-1 { + pwms =3D <&pwm1 0 40000 0>; + tach-ch =3D /bits/ 8 <0x1 0x9>; + }; + }; +... --=20 2.25.1 From nobody Thu Sep 24 13:01:01 2026 Received: from CHN02-BJS-obe.outbound.protection.partner.outlook.cn (mail-bjschn02on2119.outbound.protection.partner.outlook.cn [139.219.17.119]) (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 E31EB524B16; Wed, 23 Sep 2026 13:08:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=139.219.17.119 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790168892; cv=fail; b=UbHVK/UXYecfw/thbrUup3hA8o1rAtsa9am5SerVtX4DOGpygUTSb8LfQ7xlGBXI6m5Vk5YtnaxTQIx1D0wBmA8p5dJHPk1GLofrOcUEOJF6Ska1sMQ5SARNBWKlNWc+zfibnDii/z4Q4kObMXyG7FSIwug4lOyB6A5xy3pGCAs= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790168892; c=relaxed/simple; bh=VxXpY0MkGy4j81QpzTUnsQKv2LXWooKmc1g3rjZsUD8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: Content-Type:MIME-Version; b=KGb8WsC74/GIs+TDWyevsz0dYq4x4B6A67MuwtZRuoAF0axVighp9ZFFG/LZ48AhzxtiWDzyYD00TSWXIK6tGSLHxUHr6blLy3DFrSN0epx8VUDadYlXo+afcwLRQqIEC6F5QDuM/A1Hkiwy4kO3n7UZ+oeeVDTqv2NlKw2lwok= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=starfivetech.com; spf=pass smtp.mailfrom=starfivetech.com; arc=fail smtp.client-ip=139.219.17.119 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=starfivetech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=starfivetech.com ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=Y6nwGdFoQmD2fknHcIDDpH/zPe2K+6SIhgvF3zk5aJZyXV5gQuvb8AGWkuzS7Hxyp3YXgGeO6Xzuyh71wpWbb5sUHlb/EJUI4xepz1pDd4ls9EkLjPd9OlgEDkMW3tDdyDDqd7Lv+7p/O+Zzs1nZ0a/8GZvZoVBxkuxM0toRR64u8Mg3AzXtVTZ+jy3yh1K8Dar5gY2XhilAnhCXLTgHink9lQXA8SK1lwMTTcBewXkEGw82TTUlkevytZoUeu1LDRoxVEcLUSpfjYPsePcWbwaXbWKpyiMS0cwMA8OV5za+GxyWR/l6K5PkjPj77ZS+BUqR0KjxkAZJadYyhuH9+Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; 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=Bi4VO4cd6RfeFV+1KBC8yc78hWL9eeghuYbIz+mDwGU=; b=XOqItN70jmy09cZcmd8bARL/aSwmdFyNBgxepTtn0WzVNf84KBsGSIO9rRG+VUERVBbFm9Fp34NRG3lv6X6os9EngKCeSCCtNC8YvtsmcrgmLA/ybvpj81HrEL3KZq5waXeBy1ulXhpIQtpRQgOaqSG1nO25WJTb1GKlxzflOeCDTYSbtC2m0X6CsUdEAYZgXHthMIjsmpaREOr7VZlIcQodBfpkwSBN+viidLmZsto3bBBCF4+a1R4vuyYWHaaXrxjbjPQytl8Pacunqwuia2QVlw2RpyFqgxETvvJnnnFxlgjWqvhE6GLfpz0L8D+QiXKQiNeAr97/UvpwQn32Aw== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=starfivetech.com; dmarc=pass action=none header.from=starfivetech.com; dkim=pass header.d=starfivetech.com; arc=none Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=starfivetech.com; Received: from ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn (2406:e500:c550:17::6) by ZQ4PR01MB1315.CHNPR01.prod.partner.outlook.cn (2406:e500:c550:15::11) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.451.17; Wed, 23 Sep 2026 13:07:51 +0000 Received: from ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn ([fe80::e7d4:256c:b066:850d]) by ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn ([fe80::e7d4:256c:b066:850d%4]) with mapi id 15.21.0451.014; Wed, 23 Sep 2026 13:07:51 +0000 From: Changhuang Liang To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, Changhuang Liang Subject: [PATCH v5 2/2] hwmon: (starfive-pwm-fan) Add PWM fan driver for StarFive JHB100 Date: Wed, 23 Sep 2026 06:06:24 -0700 Message-Id: <20260923130624.239499-3-changhuang.liang@starfivetech.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260923130624.239499-1-changhuang.liang@starfivetech.com> References: <20260923130624.239499-1-changhuang.liang@starfivetech.com> Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: SHXPR01CA0008.CHNPR01.prod.partner.outlook.cn (10.43.110.209) To ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn (10.2.3.102) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: ZQ4PR01MB1202:EE_|ZQ4PR01MB1315:EE_ X-MS-Office365-Filtering-Correlation-Id: 9b2a7bf5-8b5c-4c23-feab-08df1973ac0e X-MS-Exchange-SenderADCheck: 1 X-Microsoft-Antispam: BCL:0;ARA:13230040|366016|376014|52116014|23010399003|1800799024|38350700014|6133799003|3023799007|10067099003|56012099006|5023799004|22082099003|18002099003; X-Microsoft-Antispam-Message-Info: tzdfFXFKkQkBtvZDc0JpnJ/2ETrEhe7hNjZdA1rePXEawv0NVBD+byWZM7MpkIXqREf4/eKixmi9w/ZAy7sWaRO7tGYXl0siInCrZS+FekBQcCjjMBjEz1+xNV9wF8R+UHMIKl3KiuAH05ie1ND6ZLuDllZTkkKnq2KdXZiEhxPqZoaPnpjAsOp0BScF2NiwpG0Uk4/uYto5GZXuhlwbqIiElmeB9dS5jgR/rF0qdqHWxOcqVPF8gE+H4DCXdSo/EyOf3oUY7NcMBVV9ZYcNouRkZET+uP1Z/2rA+x/2hGz61/pqzkmwr88WVJpoY7GHA5GGBgsXv9JSfyzdTmyQyleuvtIL172C7Szwcl6VJH3IKejCEZj3IphaZY7znOzmxUKXnafqOJDWLRscNPZ2kPrB9GUJQLP4gE1EBkz0BsoCS//IVeL4DwXkvW/AgkXNIWUz57tNkOcA5T2OO3rE75NyyVvzyIn/yFc3N4b7GYmrSA1ml0dL2nfMb82sBgD/Snll6dwfSQQNR3MLLg2RjlofkqN3+7ac/E59FGbXyt+LmkR3s5T2GtoeSIvbh4Nz X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn;PTR:;CAT:NONE;SFS:(13230040)(366016)(376014)(52116014)(23010399003)(1800799024)(38350700014)(6133799003)(3023799007)(10067099003)(56012099006)(5023799004)(22082099003)(18002099003);DIR:OUT;SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?5NZIzNYXQ2p0AZGPYZmuoRCgFQN0k65VY7I/PkvqhwlWMlZD9I8Y3pMS9rhA?= =?us-ascii?Q?kHlGc7IL/AofglTlqVaV7Z3ii4m9GktN4fNkrNXorYgoQlz2p/w0dgx7WMsU?= =?us-ascii?Q?dcZMWdwgXbHc6TjPs0F8orNeVOSiEtIM3kxGE/GZQLpGYBEnsk2YGeG/6Pn3?= =?us-ascii?Q?EbhChhrVZokiLDNmhTh+7xMW2Jxbe3ujEfmO3XqTakKRfhlDy32A0z2ZVPu/?= =?us-ascii?Q?yNCag+FtI8cz5NwaP/Mj+VKTE2JAgXCng/0AM4zzSzchW3tuXHq64+UO3a4/?= =?us-ascii?Q?Bk/OJW0DV2isZYMmWpEx28XgAlyrPS6iItqnfSIiMzIH2EtOj5f1kyqLG4u2?= =?us-ascii?Q?3txw1RrD6qpZ+MBz5DHqEg0LYHglpbkwp0kdQgiUFPRPlVMhMfH+QhYV4bvd?= =?us-ascii?Q?fSWxlP9NqZRdVLkz7UiEGDrbthqXMmsf7tNjdx3kpmP/4mI8iY+jmiv3CQ3v?= =?us-ascii?Q?0vWyFOEWqSZNOQazj4Yjv1c0NP+vDRESgr5YBpaVEKxUBM+gWM970HGmZ549?= =?us-ascii?Q?XK6qOB5/sqtXsX5uDTU9ER428I+/w20bReRN1IoBxvxpZCPNUmCgRW3L4Frv?= =?us-ascii?Q?q/R89Ei3DL/YDl/+cPxYAxqd+LHUZoKmbO3lGh8hIaJtkQr0R5Fh6XsE59gR?= =?us-ascii?Q?T++tOKrZsJTYXPRLdCKhSB1UoOpA4WfDo1ImwtZyAPTW4y99LL1Yr/fSWfKs?= =?us-ascii?Q?jmdwrMDNuEPBp02Uq5mLSG9EFKIUItytaupFRkFWkXSpe96H0djMm0V4hlcm?= =?us-ascii?Q?JGXGlD0ZWZm0ze0nmHJWJ0zycrkUDYT44iYiFlbLVDkgSxe2QmkAmFVW4a6E?= =?us-ascii?Q?pXSJsGrjNGxn9Q9XUPLPaVJ+mQ76wNReQDBb2ZDJwBH4CO9c0FD2EhmYzaE7?= =?us-ascii?Q?suoAfUGPUj7ZcyQCd7em9KFTpns8uFWVjZF+DlOZeN5wEWbB0BJtIceZpnfb?= =?us-ascii?Q?WCkmRIZTBekxCG9/He+rU/YzpaU02roAXLO9BL24FHKaGDuli9311/4Dl3ew?= =?us-ascii?Q?CIe2w16ATPU2RNxScD09JJ4hYKSjMEKQNi90rJKhC4OPQN9cICCwKy7lbuqG?= =?us-ascii?Q?F/B7zEK6pZDeCzWUzeZysXseNvH4xQ9MZjnx6PNfGXVrVW7e3efm3QYjJ22k?= =?us-ascii?Q?5iXMc0ZQqELneAminVUSAQpeiKkgaABRrbqkib1SpVZc7LG6dV/ArhF0guut?= =?us-ascii?Q?oAikZSvkI8mvQCNVsB+TjWzFVXjH13GCRDlMOh4Ywd+f28J5W1Sb+IoeyC6x?= =?us-ascii?Q?oGQzEDjLEdKXC82tmogeK5ZvHR/rOMUYpI/TD7s9lqsJCkr6ORSvNn8tKSUW?= =?us-ascii?Q?7fgQVx+/RX+uSTYLWRPjYL+m1jFqIhZw4W64sMLZOXcSVvTRL0BN4hAwwwOE?= =?us-ascii?Q?PawVV9BGXS6BTbHZkZ/lBLIWS4ZcL2e1XUxKBYgamVZUQhEZOYMM/+cIL4Fz?= =?us-ascii?Q?SWNRydgft2C+P9C3+D5bDFzJLkgDpzWUNYnXpv/vpttJKWfxz9R032m/hSLC?= =?us-ascii?Q?OgxHlHldJo1WjWaIpklxJ1uOyPTnqPigu5FSHvL5paeqSjbH/D4f7/Ts8DNL?= =?us-ascii?Q?IhrxpsG22YE387KM/OXlDOPC+MulA8zTwVQTXlhhQPulZPNgHrHyNXm0GcI3?= =?us-ascii?Q?1tVknv8fETtkT3nHVZCkarLfmVkfPJiFiMmA++qYyqTPY4Y4vDDhbTqxoumX?= =?us-ascii?Q?5m0ndNCHARkpDKV5renLVvGZfX6lFros4Cz0+2lrzoFjtm63UIIHU3l60aB7?= =?us-ascii?Q?ArDN2roPYg/gBSwt0jkoleSG7O+e0wzD07CJUz/CIj4PBfF8X2yA?= X-OriginatorOrg: starfivetech.com X-MS-Exchange-CrossTenant-Network-Message-Id: 9b2a7bf5-8b5c-4c23-feab-08df1973ac0e X-MS-Exchange-CrossTenant-AuthSource: ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 23 Sep 2026 13:07:51.0253 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 06fe3fa3-1221-43d3-861b-5a4ee687a85c X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: xnJ5Kj8nAIIWwV12Ae7bSZ19aUA5/U19zZEGXB7G0GKH9YN4Po4hhscY1V5o6QggBgjkBincIaSAZbo+4+P4HvxXOi8r9JYQfrvrA1fdirayK76K6cfspqlSbNC+pgl9 X-MS-Exchange-Transport-CrossTenantHeadersStamped: ZQ4PR01MB1315 Content-Type: text/plain; charset="utf-8" Add PWM fan controller driver for StarFive JHB100 SoC. Fan speed is controlled through up to 8 PWM channels supplied by an external PWM controller and exposed as pwm[1-8], and one PWM channel can drive up to two fans. The tachometer block supports 16 independent inputs and reports fan speed in RPM through the hwmon sysfs interface, along with stall and low-speed alarms. For this controller, the special clock and reset operation sequence is: probe: clk_prepare_enable() then reset_control_deassert() remove: clk_disable_unprepare() then reset_control_assert() Co-developed-by: William Qiu Signed-off-by: William Qiu Signed-off-by: Changhuang Liang --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/starfive-pwm-fan.rst | 45 ++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 15 + drivers/hwmon/Makefile | 1 + drivers/hwmon/starfive-pwm-fan.c | 762 +++++++++++++++++++++++ 6 files changed, 832 insertions(+) create mode 100644 Documentation/hwmon/starfive-pwm-fan.rst create mode 100644 drivers/hwmon/starfive-pwm-fan.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 199ca79621e9..949b46dcc258 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -263,6 +263,7 @@ Hardware Monitoring Kernel Drivers sparx5-temp spd5118 sq24860 + starfive-pwm-fan stpddc60 surface_fan sy7636a-hwmon diff --git a/Documentation/hwmon/starfive-pwm-fan.rst b/Documentation/hwmon= /starfive-pwm-fan.rst new file mode 100644 index 000000000000..c61c01088b78 --- /dev/null +++ b/Documentation/hwmon/starfive-pwm-fan.rst @@ -0,0 +1,45 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver starfive-pwm-fan +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D + +Supported chips: + + * StarFive JHB100 + + Prefix: 'starfive_pwm_fan' + + Addresses scanned: - + +Authors: + - Changhuang Liang + +Description +----------- + +This driver implements support for the PWM fan controller found on the +StarFive JHB100 SoC. Fan speed is controlled through a PWM signal supplied +by 8 PWM channels of an external PWM controller, and measured by a hardware +tachometer block with 16 independent tachometer inputs. Each tachometer ch= annel +measures the number of pulses within a fixed 100 ms window. + +Each fan-N child node in the devicetree references one PWM channel, which +may drive up to two fans, and lists the tachometer inputs wired to them. + +Sysfs entries +------------- + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D +pwm[1-8] PWM duty cycle (read/write), 0-255. pwmN corresponds + to the Nth fan child node. +fan[1-16]_input Fan speed in RPM (read-only), without a fan connecte= d, + reading this value can be significantly slow. +fan[1-16]_min Lower fan speed limit in RPM (read/write) +fan[1-16]_enable Enable/disable the tachometer channel (read/write) +fan[1-16]_fault Fan stall indication (read-only), with the fan runni= ng + normally, reading this value can be significantly sl= ow. +fan[1-16]_min_alarm Fan speed below fan[1-16]_min (read-only), when + fan[1-16]_input is greater than or equal to + fan[1-16]_min, reading this value can be significant= ly + slow. +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D diff --git a/MAINTAINERS b/MAINTAINERS index dccddc99ac9a..3735e87da3bf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26483,6 +26483,14 @@ S: Supported F: Documentation/devicetree/bindings/interrupt-controller/starfive,jhb100-= intc.yaml F: drivers/irqchip/irq-starfive-jhb100-intc.c =20 +STARFIVE JHB100 PWM FAN DRIVER +M: Changhuang Liang +L: linux-hwmon@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/hwmon/starfive,jhb100-pwm-fan.yaml +F: Documentation/hwmon/starfive-pwm-fan.rst +F: drivers/hwmon/starfive-pwm-fan.c + STATIC BRANCH/CALL M: Peter Zijlstra M: Josh Poimboeuf diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 18c682e1a475..f0d85da48956 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2467,6 +2467,21 @@ config SENSORS_SPD5118_DETECT =20 If unsure, say Y. =20 +config SENSORS_STARFIVE_PWM_FAN + tristate "StarFive JHB100 PWM fan controller" + depends on ARCH_STARFIVE || COMPILE_TEST + depends on HAS_IOMEM + depends on PWM + help + This driver supports the PWM fan controller found on StarFive SoCs + such as the JHB100. It uses up to 8 PWM outputs supplied by an + external PWM controller to control fan speed, and a tachometer block + with 16 inputs to report fan speed in RPM through the hwmon sysfs + interface, along with stall and low-speed alarms. + + This driver can also be built as a module. If so, the module + will be called starfive-pwm-fan. + config SENSORS_TC74 tristate "Microchip TC74" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index d4c345ed7261..2b04216bfcf1 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -231,6 +231,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+=3D smsc47m192.o obj-$(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON) +=3D socfpga-hwmon.o obj-$(CONFIG_SENSORS_SPARX5) +=3D sparx5-temp.o obj-$(CONFIG_SENSORS_SPD5118) +=3D spd5118.o +obj-$(CONFIG_SENSORS_STARFIVE_PWM_FAN) +=3D starfive-pwm-fan.o obj-$(CONFIG_SENSORS_STTS751) +=3D stts751.o obj-$(CONFIG_SENSORS_SURFACE_FAN)+=3D surface_fan.o obj-$(CONFIG_SENSORS_SURFACE_TEMP)+=3D surface_temp.o diff --git a/drivers/hwmon/starfive-pwm-fan.c b/drivers/hwmon/starfive-pwm-= fan.c new file mode 100644 index 000000000000..aba016e55960 --- /dev/null +++ b/drivers/hwmon/starfive-pwm-fan.c @@ -0,0 +1,762 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PWM fan controller driver for StarFive JHB100 + * + * Copyright (C) 2018-2026 StarFive Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STARFIVE_FAN_TACH_CH 16 +#define STARFIVE_FAN_MAX 8 +#define STARFIVE_FAN_TACH_PER_FAN 2 +#define STARFIVE_FAN_PWM_VAL_MAX 255 + +/* Fan-tach register offset */ +#define STARFIVE_FAN_TACH_STATUS 0x0c + +#define STARFIVE_FAN_TACH_SPEED(ch) (((ch) * 0x04) + 0x10) +#define STARFIVE_FAN_TACH_SPEED_VALID BIT(31) +#define STARFIVE_FAN_TACH_VALUE_MASK GENMASK(30, 0) + +#define STARFIVE_FAN_TACH_THRESHOLD(ch) (((ch) * 0x04) + 0x50) + +#define STARFIVE_FAN_TACH_INT_EN 0x90 +#define STARFIVE_FAN_TACH_STALL_INT_MASK GENMASK(15, 0) +#define STARFIVE_FAN_TACH_SLOW_INT_MASK GENMASK(31, 16) +#define STARFIVE_FAN_TACH_STALL_INT(ch) BIT(ch) +#define STARFIVE_FAN_TACH_SLOW_INT(ch) BIT((ch) + 16) + +#define STARFIVE_FAN_TACH_MEASURE_TIME 0x94 + +#define STARFIVE_FAN_TACH_CH_EN 0x98 +#define STARFIVE_FAN_TACH_EN(ch) BIT(ch) + +#define STARFIVE_FAN_DEFAULT_PULSE_PR 2 +#define STARFIVE_FAN_DEFAULT_MEASURE_RATIO 10 +#define STARFIVE_FAN_DEFAULT_RPM_PAUSE_TIME (60 * STARFIVE_FAN_DEFAULT_MEA= SURE_RATIO) + +#define STARFIVE_FAN_TACH_TIMEOUT \ + (USEC_PER_SEC / STARFIVE_FAN_DEFAULT_MEASURE_RATIO) + +#define STARFIVE_FAN_TACH_TIMEOUT_JIFFIES \ + (msecs_to_jiffies(1000) / STARFIVE_FAN_DEFAULT_MEASURE_RATIO) + +#define FAN_ATTRIBUTE_SET \ + (HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ENABLE | \ + HWMON_F_FAULT | HWMON_F_MIN_ALARM) + +struct starfive_pwm_fan { + struct pwm_device *pwm; + struct pwm_state pwm_state; + unsigned int pwm_value; +}; + +struct starfive_pwm_fan_data { + void __iomem *regs; + struct reset_control *rst; + struct clk *clk; + /* Serializes PWM updates with PM operations */ + struct mutex pwm_lock; + struct starfive_pwm_fan fans[STARFIVE_FAN_MAX]; + bool tach_present[STARFIVE_FAN_TACH_CH]; + u8 pulses_per_rev[STARFIVE_FAN_TACH_CH]; + struct completion comp_stall[STARFIVE_FAN_TACH_CH]; + struct completion comp_slow[STARFIVE_FAN_TACH_CH]; + u8 fan_stall[STARFIVE_FAN_TACH_CH]; + u8 fan_slow[STARFIVE_FAN_TACH_CH]; + u32 armed_stall; + u32 armed_slow; + /* protects fan_stall[]/fan_slow[]/armed_* and the STATUS register */ + spinlock_t lock; +}; + +static void starfive_fan_tach_ch_enable(struct starfive_pwm_fan_data *priv= , u8 tach_ch, + bool enable) +{ + if (enable) { + writel(readl(priv->regs + STARFIVE_FAN_TACH_CH_EN) | + STARFIVE_FAN_TACH_EN(tach_ch), + priv->regs + STARFIVE_FAN_TACH_CH_EN); + } else { + writel(readl(priv->regs + STARFIVE_FAN_TACH_CH_EN) & + ~(STARFIVE_FAN_TACH_EN(tach_ch)), + priv->regs + STARFIVE_FAN_TACH_CH_EN); + } +} + +static void starfive_fan_tach_ch_stall_unmask(struct starfive_pwm_fan_data= *priv, u8 tach_ch, + bool unmask) +{ + if (unmask) { + writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) | + STARFIVE_FAN_TACH_STALL_INT(tach_ch), + priv->regs + STARFIVE_FAN_TACH_INT_EN); + } else { + writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) & + ~STARFIVE_FAN_TACH_STALL_INT(tach_ch), + priv->regs + STARFIVE_FAN_TACH_INT_EN); + } +} + +static void starfive_fan_tach_ch_slow_unmask(struct starfive_pwm_fan_data = *priv, u8 tach_ch, + bool unmask) +{ + if (unmask) { + writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) | + STARFIVE_FAN_TACH_SLOW_INT(tach_ch), + priv->regs + STARFIVE_FAN_TACH_INT_EN); + } else { + writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) & + ~STARFIVE_FAN_TACH_SLOW_INT(tach_ch), + priv->regs + STARFIVE_FAN_TACH_INT_EN); + } +} + +static u32 starfive_fan_tach_rpm_to_val(struct starfive_pwm_fan_data *priv= , u8 tach_ch, u32 rpm) +{ + u64 tach_val; + u8 ppr; + + ppr =3D priv->pulses_per_rev[tach_ch]; + + tach_val =3D (u64)rpm * ppr; + tach_val =3D div_u64(tach_val, STARFIVE_FAN_DEFAULT_RPM_PAUSE_TIME); + + return min_t(u64, tach_val, STARFIVE_FAN_TACH_VALUE_MASK); +} + +static long starfive_fan_tach_val_to_rpm(struct starfive_pwm_fan_data *pri= v, u8 tach_ch, + u32 tach_val) +{ + u64 rpm; + u8 ppr; + + ppr =3D priv->pulses_per_rev[tach_ch]; + + rpm =3D (u64)tach_val * STARFIVE_FAN_DEFAULT_RPM_PAUSE_TIME; + + return div_u64(rpm, ppr); +} + +static int starfive_fan_tach_get_rpm(struct starfive_pwm_fan_data *priv, + u8 tach_ch, long *rpm) +{ + u32 val; + int ret; + + ret =3D readl_poll_timeout(priv->regs + STARFIVE_FAN_TACH_SPEED(tach_ch), + val, val & STARFIVE_FAN_TACH_SPEED_VALID, + 100, STARFIVE_FAN_TACH_TIMEOUT); + if (ret) + return -ENODATA; + + val =3D FIELD_GET(STARFIVE_FAN_TACH_VALUE_MASK, val); + if (!val) { + /* Sampling =3D 0 may mean no valid capture; wait 1.5 more cycles for va= lid read. */ + fsleep(STARFIVE_FAN_TACH_TIMEOUT + STARFIVE_FAN_TACH_TIMEOUT / 2); + ret =3D readl_poll_timeout(priv->regs + STARFIVE_FAN_TACH_SPEED(tach_ch), + val, val & STARFIVE_FAN_TACH_SPEED_VALID, + 100, STARFIVE_FAN_TACH_TIMEOUT); + if (ret) + return -ENODATA; + + val =3D FIELD_GET(STARFIVE_FAN_TACH_VALUE_MASK, val); + } + + *rpm =3D starfive_fan_tach_val_to_rpm(priv, tach_ch, val); + + return 0; +} + +static long starfive_fan_tach_get_rpm_threshold(struct starfive_pwm_fan_da= ta *priv, + u8 tach_ch) +{ + u32 val; + + val =3D readl(priv->regs + STARFIVE_FAN_TACH_THRESHOLD(tach_ch)); + + val =3D FIELD_GET(STARFIVE_FAN_TACH_VALUE_MASK, val); + + return starfive_fan_tach_val_to_rpm(priv, tach_ch, val); +} + +static void starfive_set_tach_rpm_threshold(struct starfive_pwm_fan_data *= priv, + u8 tach_ch, u32 val) +{ + u32 reg_val; + + reg_val =3D starfive_fan_tach_rpm_to_val(priv, tach_ch, val); + reg_val =3D clamp_val(reg_val, 1, STARFIVE_FAN_TACH_VALUE_MASK); + + writel(reg_val, priv->regs + STARFIVE_FAN_TACH_THRESHOLD(tach_ch)); +} + +static bool starfive_fan_tach_get_ch_en(struct starfive_pwm_fan_data *priv, + u8 tach_ch) +{ + u32 enable; + + enable =3D readl(priv->regs + STARFIVE_FAN_TACH_CH_EN); + + return !!(enable & STARFIVE_FAN_TACH_EN(tach_ch)); +} + +static int starfive_pwm_fan_set_pwm(struct starfive_pwm_fan_data *priv, + int channel, unsigned int pwm) +{ + struct starfive_pwm_fan *fan =3D &priv->fans[channel]; + struct pwm_state state; + int ret; + + guard(mutex)(&priv->pwm_lock); + + state =3D fan->pwm_state; + state.duty_cycle =3D DIV_ROUND_UP_ULL((u64)pwm * state.period, + STARFIVE_FAN_PWM_VAL_MAX); + /* + * Keep the PWM enabled even at zero duty cycle so the output stays + * driven low instead of being left floating by the PWM hardware. + */ + state.enabled =3D true; + + ret =3D pwm_apply_might_sleep(fan->pwm, &state); + if (ret) + return ret; + + fan->pwm_state =3D state; + fan->pwm_value =3D pwm; + + return 0; +} + +static int starfive_pwm_fan_pwm_read(struct starfive_pwm_fan_data *priv, + u32 attr, int channel, long *val) +{ + switch (attr) { + case hwmon_pwm_input: + scoped_guard(mutex, &priv->pwm_lock) + *val =3D priv->fans[channel].pwm_value; + + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int starfive_pwm_fan_fan_read(struct starfive_pwm_fan_data *priv, + u32 attr, int channel, long *val) +{ + int ret; + + switch (attr) { + case hwmon_fan_fault: + scoped_guard(spinlock_irqsave, &priv->lock) { + writel(STARFIVE_FAN_TACH_STALL_INT(channel), + priv->regs + STARFIVE_FAN_TACH_STATUS); + /* clear fan_stall first */ + priv->fan_stall[channel] =3D 0; + reinit_completion(&priv->comp_stall[channel]); + priv->armed_stall |=3D BIT(channel); + } + + starfive_fan_tach_ch_stall_unmask(priv, channel, true); + + /* Waiting for hardware to measure */ + wait_for_completion_timeout(&priv->comp_stall[channel], + 2 * STARFIVE_FAN_TACH_TIMEOUT_JIFFIES); + + starfive_fan_tach_ch_stall_unmask(priv, channel, false); + + scoped_guard(spinlock_irqsave, &priv->lock) { + priv->armed_stall &=3D ~BIT(channel); + *val =3D priv->fan_stall[channel]; + } + + break; + case hwmon_fan_input: + if (!starfive_fan_tach_get_ch_en(priv, channel)) + return -ENODATA; + + ret =3D starfive_fan_tach_get_rpm(priv, channel, val); + if (ret < 0) + return ret; + + break; + case hwmon_fan_min: + *val =3D starfive_fan_tach_get_rpm_threshold(priv, channel); + + break; + case hwmon_fan_min_alarm: + scoped_guard(spinlock_irqsave, &priv->lock) { + writel(STARFIVE_FAN_TACH_SLOW_INT(channel), + priv->regs + STARFIVE_FAN_TACH_STATUS); + /* clear fan_slow first */ + priv->fan_slow[channel] =3D 0; + reinit_completion(&priv->comp_slow[channel]); + priv->armed_slow |=3D BIT(channel); + } + + starfive_fan_tach_ch_slow_unmask(priv, channel, true); + + /* Waiting for hardware to measure */ + wait_for_completion_timeout(&priv->comp_slow[channel], + 2 * STARFIVE_FAN_TACH_TIMEOUT_JIFFIES); + + starfive_fan_tach_ch_slow_unmask(priv, channel, false); + + scoped_guard(spinlock_irqsave, &priv->lock) { + priv->armed_slow &=3D ~BIT(channel); + *val =3D priv->fan_slow[channel]; + } + + break; + case hwmon_fan_enable: + *val =3D starfive_fan_tach_get_ch_en(priv, channel); + + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int starfive_pwm_fan_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct starfive_pwm_fan_data *priv =3D dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + return starfive_pwm_fan_pwm_read(priv, attr, channel, val); + case hwmon_fan: + return starfive_pwm_fan_fan_read(priv, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int starfive_pwm_fan_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct starfive_pwm_fan_data *priv =3D dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + if (attr !=3D hwmon_pwm_input) + return -EOPNOTSUPP; + + if (val < 0 || val > STARFIVE_FAN_PWM_VAL_MAX) + return -EINVAL; + + return starfive_pwm_fan_set_pwm(priv, channel, val); + case hwmon_fan: + switch (attr) { + case hwmon_fan_min: + if (val < 0 || val > U32_MAX) + return -EINVAL; + + starfive_set_tach_rpm_threshold(priv, channel, val); + + return 0; + case hwmon_fan_enable: + if (val !=3D 0 && val !=3D 1) + return -EINVAL; + + starfive_fan_tach_ch_enable(priv, channel, val); + + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static umode_t starfive_pwm_fan_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct starfive_pwm_fan_data *priv =3D drvdata; + + switch (type) { + case hwmon_pwm: + if (!priv->fans[channel].pwm) + return 0; + + if (attr =3D=3D hwmon_pwm_input) + return 0644; + + return 0; + case hwmon_fan: + if (!priv->tach_present[channel]) + return 0; + + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_fault: + case hwmon_fan_min_alarm: + return 0444; + case hwmon_fan_min: + case hwmon_fan_enable: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +static const struct hwmon_ops starfive_pwm_fan_ops =3D { + .is_visible =3D starfive_pwm_fan_is_visible, + .read =3D starfive_pwm_fan_read, + .write =3D starfive_pwm_fan_write, +}; + +static const struct hwmon_channel_info * const starfive_pwm_fan_info[] =3D= { + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, HWMON_PWM_INPUT, + HWMON_PWM_INPUT, HWMON_PWM_INPUT, + HWMON_PWM_INPUT, HWMON_PWM_INPUT, + HWMON_PWM_INPUT, HWMON_PWM_INPUT), + HWMON_CHANNEL_INFO(fan, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET, + FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET), + NULL +}; + +static const struct hwmon_chip_info starfive_pwm_fan_chip_info =3D { + .ops =3D &starfive_pwm_fan_ops, + .info =3D starfive_pwm_fan_info, +}; + +static int starfive_pwm_fan_create_fan(struct device *dev, struct device_n= ode *child, + struct starfive_pwm_fan_data *priv, + unsigned int fan_idx) +{ + struct starfive_pwm_fan *fan =3D &priv->fans[fan_idx]; + u8 tach_ch[STARFIVE_FAN_TACH_PER_FAN]; + struct pwm_device *pwm; + int ret, count; + u32 ppr, index; + + count =3D of_property_count_u8_elems(child, "tach-ch"); + if (count < 1 || count > STARFIVE_FAN_TACH_PER_FAN) + return dev_err_probe(dev, -EINVAL, "%pOF: invalid tach-ch\n", child); + + ret =3D of_property_read_u8_array(child, "tach-ch", tach_ch, count); + if (ret) + return dev_err_probe(dev, ret, "%pOF: failed to read tach-ch\n", child); + + /* Parse pulses-per-revolution, default to 2 if not specified */ + ppr =3D STARFIVE_FAN_DEFAULT_PULSE_PR; + of_property_read_u32(child, "pulses-per-revolution", &ppr); + + if (!ppr || ppr > 4) + return dev_err_probe(dev, -EINVAL, + "%pOF: invalid pulses-per-revolution %u, must be 1-4\n", + child, ppr); + + pwm =3D devm_fwnode_pwm_get(dev, of_fwnode_handle(child), NULL); + if (IS_ERR(pwm)) + return dev_err_probe(dev, PTR_ERR(pwm), "%pOF: could not get PWM\n", chi= ld); + + pwm_init_state(pwm, &fan->pwm_state); + if (!fan->pwm_state.period) + return dev_err_probe(dev, -EINVAL, "%pOF: PWM period is zero\n", child); + + fan->pwm =3D pwm; + + for (index =3D 0; index < count; index++) { + u8 ch =3D tach_ch[index]; + + if (ch >=3D STARFIVE_FAN_TACH_CH) + return dev_err_probe(dev, -EINVAL, "%pOF: invalid tach-ch %u\n", + child, ch); + + if (priv->tach_present[ch]) + return dev_err_probe(dev, -EINVAL, "%pOF: duplicate tach-ch %u\n", + child, ch); + + priv->pulses_per_rev[ch] =3D ppr; + priv->tach_present[ch] =3D true; + starfive_fan_tach_ch_enable(priv, ch, true); + } + + return 0; +} + +static irqreturn_t starfive_pwm_fan_irq(int irq, void *dev_id) +{ + struct starfive_pwm_fan_data *priv =3D dev_id; + u32 fan_status; + int i; + + guard(spinlock)(&priv->lock); + + fan_status =3D readl(priv->regs + STARFIVE_FAN_TACH_STATUS); + if (!fan_status) + return IRQ_NONE; + + writel(fan_status, priv->regs + STARFIVE_FAN_TACH_STATUS); + + for (i =3D 0; i < STARFIVE_FAN_TACH_CH; i++) { + if (!priv->tach_present[i]) + continue; + + if ((fan_status & STARFIVE_FAN_TACH_STALL_INT(i)) && + (priv->armed_stall & BIT(i))) { + priv->fan_stall[i] =3D 1; + complete(&priv->comp_stall[i]); + } + + if ((fan_status & STARFIVE_FAN_TACH_SLOW_INT(i)) && + (priv->armed_slow & BIT(i))) { + priv->fan_slow[i] =3D 1; + complete(&priv->comp_slow[i]); + } + } + + return IRQ_HANDLED; +} + +static int starfive_pwm_fan_pwm_disable_all(struct starfive_pwm_fan_data *= priv) +{ + struct pwm_state state; + int i, ret; + + guard(mutex)(&priv->pwm_lock); + + for (i =3D 0; i < STARFIVE_FAN_MAX; i++) { + struct starfive_pwm_fan *fan =3D &priv->fans[i]; + + if (!fan->pwm || !fan->pwm_state.enabled) + continue; + + state =3D fan->pwm_state; + state.duty_cycle =3D 0; + state.enabled =3D false; + + ret =3D pwm_apply_might_sleep(fan->pwm, &state); + if (ret) + return ret; + } + + return 0; +} + +static void starfive_pwm_fan_pwm_disable(void *data) +{ + struct starfive_pwm_fan_data *priv =3D data; + + starfive_pwm_fan_pwm_disable_all(priv); +} + +static void starfive_fan_tach_disable(void *data) +{ + struct starfive_pwm_fan_data *priv =3D data; + + clk_disable_unprepare(priv->clk); + reset_control_assert(priv->rst); +} + +static int starfive_pwm_fan_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev, *dev =3D &pdev->dev; + struct starfive_pwm_fan_data *priv; + unsigned int fan_idx; + unsigned long clk_rate; + int irq; + int ret; + u32 i; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + ret =3D devm_mutex_init(dev, &priv->pwm_lock); + if (ret) + return ret; + + for (i =3D 0; i < STARFIVE_FAN_TACH_CH; i++) { + init_completion(&priv->comp_stall[i]); + init_completion(&priv->comp_slow[i]); + } + + platform_set_drvdata(pdev, priv); + + priv->regs =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return dev_err_probe(dev, PTR_ERR(priv->regs), + "Unable to map IO resources\n"); + + priv->clk =3D devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Unable to get fan tach's clock\n"); + + priv->rst =3D devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->rst)) + return dev_err_probe(dev, PTR_ERR(priv->rst), + "Unable to get fan tach's reset\n"); + + ret =3D clk_prepare_enable(priv->clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable clock\n"); + + clk_rate =3D clk_get_rate(priv->clk); + if (!clk_rate || clk_rate > U32_MAX) { + clk_disable_unprepare(priv->clk); + return dev_err_probe(dev, -EINVAL, "Invalid clock rate: %lu\n", + clk_rate); + } + + ret =3D reset_control_deassert(priv->rst); + if (ret) { + clk_disable_unprepare(priv->clk); + return dev_err_probe(dev, ret, "Failed to deassert reset\n"); + } + + ret =3D devm_add_action_or_reset(dev, starfive_fan_tach_disable, priv); + if (ret) + return ret; + + /* Mask all sources before touching status / requesting the IRQ. */ + writel(0, priv->regs + STARFIVE_FAN_TACH_INT_EN); + writel(STARFIVE_FAN_TACH_STALL_INT_MASK | STARFIVE_FAN_TACH_SLOW_INT_MASK, + priv->regs + STARFIVE_FAN_TACH_STATUS); + writel(clk_rate / STARFIVE_FAN_DEFAULT_MEASURE_RATIO, + priv->regs + STARFIVE_FAN_TACH_MEASURE_TIME); + + fan_idx =3D 0; + for_each_available_child_of_node_scoped(dev->of_node, child) { + if (fan_idx >=3D STARFIVE_FAN_MAX) + return dev_err_probe(dev, -EINVAL, + "Too many fan nodes, at most %d\n", + STARFIVE_FAN_MAX); + + ret =3D starfive_pwm_fan_create_fan(dev, child, priv, fan_idx++); + if (ret) + return ret; + } + + /* Must be registered after the PWMs are requested */ + ret =3D devm_add_action_or_reset(dev, starfive_pwm_fan_pwm_disable, priv); + if (ret) + return ret; + + for (i =3D 0; i < STARFIVE_FAN_MAX; i++) { + if (!priv->fans[i].pwm) + continue; + + ret =3D starfive_pwm_fan_set_pwm(priv, i, STARFIVE_FAN_PWM_VAL_MAX); + if (ret) + return dev_err_probe(dev, ret, + "Failed to configure PWM of fan %u\n", i); + } + + irq =3D platform_get_irq(pdev, 0); + if (irq < 0) + return dev_err_probe(dev, irq, "Failed to get IRQ\n"); + + ret =3D devm_request_irq(dev, irq, starfive_pwm_fan_irq, 0, dev_name(dev)= , priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IRQ\n"); + + hwmon_dev =3D devm_hwmon_device_register_with_info(dev, "starfive_pwm_fan= ", + priv, &starfive_pwm_fan_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return dev_err_probe(dev, PTR_ERR(hwmon_dev), + "Failed to register hwmon device\n"); + + return 0; +} + +static int starfive_pwm_fan_suspend(struct device *dev) +{ + struct starfive_pwm_fan_data *priv =3D dev_get_drvdata(dev); + + return starfive_pwm_fan_pwm_disable_all(priv); +} + +static int starfive_pwm_fan_resume(struct device *dev) +{ + struct starfive_pwm_fan_data *priv =3D dev_get_drvdata(dev); + int i, ret; + + guard(mutex)(&priv->pwm_lock); + + for (i =3D 0; i < STARFIVE_FAN_MAX; i++) { + struct starfive_pwm_fan *fan =3D &priv->fans[i]; + + if (!fan->pwm) + continue; + + ret =3D pwm_apply_might_sleep(fan->pwm, &fan->pwm_state); + if (ret) + return ret; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(starfive_pwm_fan_pm, + starfive_pwm_fan_suspend, + starfive_pwm_fan_resume); + +static const struct of_device_id starfive_pwm_fan_of_match[] =3D { + { .compatible =3D "starfive,jhb100-pwm-fan", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, starfive_pwm_fan_of_match); + +static struct platform_driver starfive_pwm_fan_driver =3D { + .probe =3D starfive_pwm_fan_probe, + .driver =3D { + .name =3D "starfive-pwm-fan", + .pm =3D pm_sleep_ptr(&starfive_pwm_fan_pm), + .of_match_table =3D starfive_pwm_fan_of_match, + }, +}; + +module_platform_driver(starfive_pwm_fan_driver); + +MODULE_AUTHOR("William Qiu "); +MODULE_AUTHOR("Changhuang Liang "); +MODULE_DESCRIPTION("StarFive JHB100 PWM fan controller driver"); +MODULE_LICENSE("GPL"); --=20 2.25.1