From patchwork Tue Aug 4 21:27:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 2940 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10]) by mxe881.netcup.net (Postfix) with ESMTPS id ED7001C0835 for ; Tue, 4 Aug 2026 23:29:04 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-25045-noreply=patchwork.local@lists.linux.dev smtp.helo=sea.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.234.253.10 as permitted sender) client-ip=172.234.253.10; envelope-from=linux-sunxi+bounces-25045-noreply=patchwork.local@lists.linux.dev; helo=sea.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id 5BFC930382B9 for ; Tue, 4 Aug 2026 21:27:41 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 3CEC445DF5A; Tue, 4 Aug 2026 21:27:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="jo6M0R0d" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-oi1-f173.google.com (mail-oi1-f173.google.com [209.85.167.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 544DA440A3D for ; Tue, 4 Aug 2026 21:27:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878861; cv=none; b=VSEPchVhDtFR7GBVDqnWcE/psgrO0JKKIZlsbtrb6rQy2Ox6XHd8wrfgOixxE6+mgpmlXUf0SvKfD66+3bmS8SVJGXZT8tDI7fJcECSRn2PQQS77+4MK/kL19K7wiumi/VdVWYK/AG2OmFepsmoymt1SBgpDu3f7xCwwyYf6Hk8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878861; c=relaxed/simple; bh=3czOY9Okboe0CqTWBtAucidFmXyF0lxRkuvX9UtfqlU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=OT1L8tB9roRXAVpLDj65HVzjPJ20VwrjbQD2vUM/wEXl035xM3VDeKwv3ha3WdNoibKD5VL2R54F9D0RqCGWTRJuhi11kA7lF7dfSb1ZOEWpXfoXjlk3PQy26hxasy8RCZR7b0DlzFJhEdjzMVVPxWjEA1s67D3ZOOTlzRvwYuc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=jo6M0R0d; arc=none smtp.client-ip=209.85.167.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-oi1-f173.google.com with SMTP id 5614622812f47-48a0ca07c38so202849b6e.2 for ; Tue, 04 Aug 2026 14:27:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785878858; x=1786483658; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=JzLX9GA0PMOy5TOxJvHxVnH9yoPV2TfmIgWpC5Rtigc=; b=jo6M0R0doYacNwxpigJdLUG2b3CYroyovEuAdANhXGRDAXeFT2PqgXiVFjJBbSXJ4b /iFX3J446yaC3LJNIhZZAWw4zvWLdpBWbrh06RbvYTGfb1AGyptyvmMSe4ymjpIY9M7r G6ST/un6ujOQQdY0APsSf+TrrLS+XESJkCaJms6hn6hqkKBCvhqBTFSCFU/rlhI/WMrE x2AMIDJheah+7zIQPHMqYcHlzojhlh6hKpCigeAB8Vhm1jqPFk8vMKdC49KuWXz9I7hU p+NwD9nboykGcF0JGQKYjG79t8zW0LOPzINYSy2IoN1udC8v08dr7UPegKyUtDbuiytY /BbQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785878858; x=1786483658; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=JzLX9GA0PMOy5TOxJvHxVnH9yoPV2TfmIgWpC5Rtigc=; b=nbsMxueVKIrtbfl8Q62+GldsONZY82y4Bpb452kAQtrlRMQfBSUQbiVE0a88BCpDUr 959XSbqBz9xbGTElahiThI4cZ8P5QzSBlq6oK0vpIe42KBL3KYgoSWOmu8E5HLaKDaif B/cQuTktVhnxOLt+uadFK6R9fdvQI2EXo0/d8WYYynX2ktRd2+q9qTf00SzYFMAIJ9Qj 7b5H83mmilX7L1Pam4ruR9bT+aL8y3iHqMwCT85Xor3S1P1PXetdnZCBhqrjKYAduZq8 Mg8P3nLnV5vrqPpwujhWdEGPd5NcgLCM8EYgAbiUYMw4DhEj9LgsrIjNccbhypOw5AAH POKQ== X-Forwarded-Encrypted: i=1; AHgh+RosHj6HCquZSUNPGn3HZoy/IAXWGAB3XLcnhpKZwBBBdg6G5E6ubKt5cBHCty2NOOGxDRiXCgjvBJJAuw==@lists.linux.dev X-Gm-Message-State: AOJu0YyIk2LF1J77rBKngaZN6EakH4MFc8aaqESi4wd9bBg9cK3gh/zD Q4G3GKnYiyY/I619N0pVc4UYzWdOLklai4qBpo2etLXb5TZIk0HLuzOB X-Gm-Gg: AR+sD12lRisDFat18UqpuU4o4LH3RgCD+Br2lE5O3RD64Aqie2a7jmXfpjTbLME1gd4 uIDOFmVnKDe6wCj5CnoE1w6XjW7A47FRwM0AJtm/leZJiP96Wg4LW/nVSg/fSi/TXiDdj9fiSt5 HliqxdbvwIN3Rt1YFe/sHRi3nYSiHeHlB7eGYz70X+/4zkkhq4yrOjtulduejIf9+vb1ycBCa6C ZPk9N+c9ZrRDEd01vkmKWNp26WWzZqcCZ1/uBP5eGNRMOf2amMx6Bmu+luU18bW9bzsrJS+tE1a pSCPYElWjEm7H994nNzKo1v7rkO5sr75zBE7SQq65YAQecNEJsme4JKE5NUYDEtdkGeAyWN42R5 /ReojgnULCM1IxNcJYKVmJaJDCsPHUXJBFNWSlZ+fnI4lKV2qeeuYtjutBmtikYcPzcppYu3g+D sxMY0VW7L+wClqfpZVz+sErrHz0YobSNSNbzqHlOb6UXbqfJj18K9Zw1FkQ/a+/IPUfhrgxKmJa 3vl9cV2mitsRf1p+i5U49DFj4c1WAU1KkFAT/zxPsz1+RIMRKUD8zJjc0tz6XlPxrtdvuFif2WY ZR59jFSX6rkyHb8v8hnZjywCVYhDA4p7d22CSQxEHjNyprorU0KkuAf4pa3M3q654YeyFdkr/rg MZrsZR8H5fxDtuMuySRSyWQwLnxTi X-Received: by 2002:a05:6808:524a:b0:496:11f1:f2ad with SMTP id 5614622812f47-4afadd7f963mr1120843b6e.6.1785878858060; Tue, 04 Aug 2026 14:27:38 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 5614622812f47-4afae7c50e9sm451659b6e.17.2026.08.04.14.27.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 14:27:37 -0700 (PDT) From: James Hilliard Date: Tue, 04 Aug 2026 15:27:24 -0600 Subject: [PATCH v8 1/4] dt-bindings: pwm: allwinner: add h616 pwm compatible Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260804-h616-pwm-v8-v8-1-db37ab8624ae@gmail.com> References: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> In-Reply-To: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Maxime Ripard , Richard Genoud , Philipp Zabel , Michael Turquette , Stephen Boyd , Brian Masney Cc: linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, Paul Kocialkowski , Thomas Petazzoni , John Stultz , Joao Schim , bigunclemax@gmail.com, linux-clk@vger.kernel.org, James Hilliard , Conor Dooley X-Mailer: b4 0.15.2 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [-1.16 / 15.00]; BAYES_HAM(-5.50)[100.00%]; RBL_SENDERSCORE(2.00)[172.234.253.10:from]; SUSPICIOUS_RECIPS(1.50)[]; DMARC_POLICY_SOFTFAIL(1.00)[gmail.com : SPF not aligned (relaxed), No valid DKIM,none]; MAILLIST(-0.15)[generic]; BAD_REP_POLICIES(0.10)[]; MIME_GOOD(-0.10)[text/plain]; HAS_LIST_UNSUB(-0.01)[]; TAGGED_RCPT(0.00)[dt]; PRECEDENCE_BULK(0.00)[]; RCPT_COUNT_TWELVE(0.00)[26]; TO_DN_SOME(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[sea.lore.kernel.org:rdns,sea.lore.kernel.org:helo,bootlin.com:email]; FORGED_SENDER_MAILLIST(0.00)[]; FREEMAIL_CC(0.00)[vger.kernel.org,lists.infradead.org,lists.linux.dev,sys-base.io,bootlin.com,google.com,schimsalabim.eu,gmail.com,microchip.com]; FREEMAIL_FROM(0.00)[gmail.com]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; R_SPF_ALLOW(0.00)[+ip4:172.234.253.10:c]; MIME_TRACE(0.00)[0:+]; RCVD_TLS_LAST(0.00)[]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; FREEMAIL_TO(0.00)[kernel.org,gmail.com,sholland.org,bootlin.com,pengutronix.de,baylibre.com,redhat.com]; FROM_HAS_DN(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; ASN(0.00)[asn:63949, ipnet:172.234.224.0/19, country:SG]; TAGGED_FROM(0.00)[bounces-25045-noreply=patchwork.local]; RCVD_COUNT_FIVE(0.00)[6]; FROM_NEQ_ENVFROM(0.00)[jameshilliard1@gmail.com,linux-sunxi@lists.linux.dev]; RCVD_VIA_SMTP_AUTH(0.00)[] X-Rspamd-Queue-Id: ED7001C0835 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= From: Richard Genoud The Allwinner H616 PWM block differs from the A10 and H6 blocks, but it uses the same input clocks as H6, so extend the existing Allwinner PWM binding. The block has six channels that can generate PWM waveforms. Each channel also has a bypass that turns its output into a clock suitable for consumers such as the integrated X-Powers AC200 or AC300 Ethernet PHY. Require #clock-cells for H616 because the bypass clocks are part of its provider interface, while continuing to disallow the property for the older variants. Acked-by: Conor Dooley Signed-off-by: Richard Genoud Signed-off-by: James Hilliard --- Changes v7 -> v8: - require both the module and bus input clocks for H616 - require #clock-cells for the H616 clock provider (reported by Sashiko) - describe the channel-indexed hardware bypass clock cells --- .../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 33 +++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml index 1197858e431f..3b1819c8630c 100644 --- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml @@ -14,6 +14,13 @@ properties: "#pwm-cells": const: 3 + "#clock-cells": + const: 1 + description: + The cell selects the PWM channel whose hardware bypass path is exported + as a clock. Its rate follows the selected shared pair source and divider; + the PWM period, duty-cycle and polarity logic is bypassed. + compatible: oneOf: - const: allwinner,sun4i-a10-pwm @@ -36,6 +43,7 @@ properties: - const: allwinner,sun50i-h5-pwm - const: allwinner,sun5i-a13-pwm - const: allwinner,sun50i-h6-pwm + - const: allwinner,sun50i-h616-pwm reg: maxItems: 1 @@ -62,7 +70,9 @@ allOf: properties: compatible: contains: - const: allwinner,sun50i-h6-pwm + enum: + - allwinner,sun50i-h6-pwm + - allwinner,sun50i-h616-pwm then: properties: @@ -83,6 +93,27 @@ allOf: clocks: maxItems: 1 + - if: + properties: + compatible: + contains: + const: allwinner,sun50i-h616-pwm + + then: + properties: + clocks: + minItems: 2 + + clock-names: + minItems: 2 + + required: + - "#clock-cells" + + else: + properties: + "#clock-cells": false + required: - compatible - reg From patchwork Tue Aug 4 21:27:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 2939 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114]) by mxe881.netcup.net (Postfix) with ESMTPS id EC9DF1C0254 for ; Tue, 4 Aug 2026 23:27:56 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.105.105.114) smtp.mailfrom=linux-sunxi+bounces-25047-noreply=patchwork.local@lists.linux.dev smtp.helo=tor.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.105.105.114 as permitted sender) client-ip=172.105.105.114; envelope-from=linux-sunxi+bounces-25047-noreply=patchwork.local@lists.linux.dev; helo=tor.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by tor.lore.kernel.org (Postfix) with ESMTP id 960D930269EC for ; Tue, 4 Aug 2026 21:27:46 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C37153AAF41; Tue, 4 Aug 2026 21:27:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="jD/L76I2" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-oi1-f180.google.com (mail-oi1-f180.google.com [209.85.167.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 30E7245A29B for ; Tue, 4 Aug 2026 21:27:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878865; cv=none; b=Ayeum28D4P01vcsJfYvDq8TNHFmVrAxyvJwMHW9+j6UqBWEXIk428qCLyaMPCzqt4yY7Yg1esxKgsypV78sbIbf9a1dPrJABH/EgGMEMTJ+8p7uRN8Piq9881CdmIgtl08TK8DxbuG/FQch5uMEgbiLP8HYeiHS+5se3LRienyo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878865; c=relaxed/simple; bh=NTdefbsX6jE4RyIWH3LDdj7P2RjrPg3sPDumNwtexOg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=jtagR9scjMfVrlZcYQSlUejOkBD0ggvt0AL4EfMYrJtb2RPieLpq6WuAaiQQgoL3QMD+Iidn47QhfcoCYDuWkdfbBKXBmCC4l565xPdOsQ+OJpV5lPJHloubulKZ2qoo3GTmEpdwuM3a9pCTlK5Zzd7gSUc2cIBMd4eWWNB4D5M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=jD/L76I2; arc=none smtp.client-ip=209.85.167.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-oi1-f180.google.com with SMTP id 5614622812f47-489f3611e0cso202864b6e.1 for ; Tue, 04 Aug 2026 14:27:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785878860; x=1786483660; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Hhlh3P9upH+rmz53AfYlVeOrKBa3e5tThI6g0S+6dZU=; b=jD/L76I2O0tDqGdXoXRL5VAT1cdTcIahCCq0zVkmpkzzQdcRsMECEm5l7myZoBx5x5 JU/+SWPdtBv15BTdg60IXpWcQwhD2CXowi1fEEXBMDB28l7HDqn+NOr7mDc4Blf06txI RjU8c0ogCIKXE5X7DOptTzqJpnVimtVI4o/3315mUjIDj8wlWoZfxbA3a/Agtv28F9WI f/URxgtXkTmCwSz9I7I9i35r7KbvAd+arrpn2plhs1mR8iygzrgJOD54ZfTtY++07OUs WU6B6ta/BzRhnCkgx8l053ph86lDTexZD/EfWnGxslGzC+VwOHO77vzGo1w5KLCOkAbC KSmQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785878860; x=1786483660; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=Hhlh3P9upH+rmz53AfYlVeOrKBa3e5tThI6g0S+6dZU=; b=PzQ3YS90/ektJm2n5VTEson+v5e65DzBYCi9jANB7D51gxC1IWDYSg0xJkgJspdKQb q1vFLLF9P38EJYeuG0+IhszbqJmF49FMppzYaDwsxWM6jRC0KV+fP7IIvAg654HDiPl7 QU9CKoGrKZ/UupSpdzN1WQMmrKgGUIA5YZ1Zn5hf/lOI2iIYWwpDzyOJrneak1d/6qAx +1tv2+kdZ5DlH1J4db1vqVVDF9nXuexx2aDvpM9yg30eMrUrfd6L/Zo7VMhli2b8w4v/ kaEBCpKCCLRSdu/VQsyCJYrqG0qYyFdFmaA15O0xL/icOH/Jt2lhJMytYhABHClqhcK5 v4Cg== X-Forwarded-Encrypted: i=1; AHgh+Rr19IivS3zUa9Y/clmYHqi/IWToO2mOaEt2ibDqxBGV7GtLnZMT7k7fwlRXm8fu/utMtGwZ0MbX0JPyUg==@lists.linux.dev X-Gm-Message-State: AOJu0YxGMphuJzbi1ON6xZT/yZ/LQ3dt9gAA4B+s7DucSJs+iU/k13e8 Dr9nHrZi1n5qxEkFuoZbYLQqunajnnVVxvYk3ywchPZAMOiUmTWwUK6Q X-Gm-Gg: AR+sD12wwTbVHpi0gkePeRHTr7WMWplzZIwt+If6RmyPMrwmZQ4N55/zOwBIz1N0f2E tgd5SVhXq5xDVNiLWeKgeeo922TMPafJRuStPOpLRjh3rAHvvvd75sVL7Edp049TVhdZaILDVER 8FvJMFtO1NJv7PJsWRD82idiL9aHJ0I0aYcVzzX3wMqwqfWeDP702o7+3vuKJry0cA5yoz66en/ cj0x21heb508eN5HwDtM58FCj5Rhg949fTtBpMaIN2fNoC+2gBkYAMWdNY01AFhdYsuQLMz3yD4 rUm51pR7EgW98NdsbA5FXs1ZgmYEo5qVeUlivsRLBvpAyxLoPFUg4BHAJ6QFbyxAmUI2mTE1f/F Un3s9gHYZra30lAiKLdoWGnp5Gds6o5ZboDzpzZozLAjjxYGaz/4AOdmYXzGQqs3ILp57W3C/3v 7CcQkcNHhhO2azxSA4Gnu0IcVxNKAc47SiVhNAUrVITerT+Rnml9hXrT+KFgympxiFGewmh21dl 46Ox+Re7TZm+hHCIbXObM8Jtnfe8SLWboVdmiCtP9opC9VMLRF/AZDP4Q8BvWpUjPQcW3j585ET msTHoEz9zUXrHwal/g4illk7NdLQcvfo1Xob0W68kQYOpMT0cLpWb6Bu+zJVgkk2V12glOv8IaO pXXXKQjAuIJwskEXT X-Received: by 2002:a05:6808:4fc6:b0:4a4:da68:9434 with SMTP id 5614622812f47-4afae1302camr1201348b6e.14.1785878859644; Tue, 04 Aug 2026 14:27:39 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 5614622812f47-4afae7c50e9sm451659b6e.17.2026.08.04.14.27.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 14:27:38 -0700 (PDT) From: James Hilliard Date: Tue, 04 Aug 2026 15:27:25 -0600 Subject: [PATCH v8 2/4] pwm: sun8i: Add H616 PWM support Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260804-h616-pwm-v8-v8-2-db37ab8624ae@gmail.com> References: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> In-Reply-To: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Maxime Ripard , Richard Genoud , Philipp Zabel , Michael Turquette , Stephen Boyd , Brian Masney Cc: linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, Paul Kocialkowski , Thomas Petazzoni , John Stultz , Joao Schim , bigunclemax@gmail.com, linux-clk@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= From: Richard Genoud Add a driver for the Allwinner H616 PWM controller, supporting six channels. Each channel output can carry either a PWM waveform or a clock from its bypass path. The channels are paired as 0/1, 2/3 and 4/5. Each pair shares a mux, gate and prescaler, while each channel has its own prescaler and bypass. Register each shared pair mux, divider and gate with the Common Clock Framework, and expose each channel bypass as a clock output. Program the channel-specific divider directly in the PWM path. Arbitrate each output when the PWM is requested or the bypass clock is prepared, and hold the shared pair rate exclusively while either sibling output is active. CCF reference counts do not describe outputs left active by firmware. Before disabling a pair gate or changing its rate, also inspect the hardware channel-enable bits. Preserve inherited outputs through generic unused-clock cleanup so declared consumers can claim them. At driver sync_state(), stop channels which remain unowned and reconcile an empty shared pair gate through CCF. This prevents both premature shutdown of a late-probing consumer and indefinite unclaimed outputs. The clock-provider interface is needed because pwm-clock cannot accurately represent the bypass path. For example, pwm-clock represents 24 MHz as an integer 42 ns PWM period. The PWM waveform-rounding rules happen to select the H616 bypass and produce 24 MHz for that request, but the pwm-clock API does not require its advertised clock rate to equal the rounded hardware waveform. Exposing the bypass through CCF provides an exact rate request, conveys that duty-cycle and polarity control are not needed, and makes the shared pair clock visible to CCF rate arbitration. Wait for the hardware period-update handshake before replacing PPR. Track the active and newly programmed periods so normal updates use a tight timeout, while an unknown firmware update conservatively allows the maximum hardware period. Scale the sleep interval to keep the number of MMIO polls bounded even for very long periods. Dead-zone mode is not representable by the PWM API. Reject it while either output in the pair is active, but clear dormant dead-zone enable state when both outputs are disabled so stale firmware configuration does not permanently prevent ordinary PWM use. Signed-off-by: Richard Genoud Co-developed-by: James Hilliard Signed-off-by: James Hilliard --- Changes v7 -> v8: - clear the complete two-bit clock-source selector field - leave rate and period registers untouched when disabling a channel - distinguish PWM-owned bypass signals from clock-owned bypass outputs - round waveforms over all parent, pair-divider, channel-divider and bypass choices according to the PWM core ordering - encode constant levels without overflowing the 16-bit active-cycle field - allocate clock topology state per device and use the fixed parent map (reported by Sashiko) - use managed clock registrations for complete probe-error cleanup (reported by Sashiko and suggested by Philipp) - deassert reset before registering clocks and keep the reset handle local (reported by Sashiko and suggested by Philipp) - arbitrate channel ownership in clock prepare/unprepare and select bypass only while the clock output is enabled (reported by Sashiko) - roll back failed PWM requests and serialize channel release (reported by Sashiko) - protect the shared pair source while either sibling channel is active (reported by Sashiko) - preserve the 65536-tick duty intermediate before polarity conversion (reported by Sashiko) - avoid replaying write-one control bits while changing channel settings (reported by Sashiko) - keep the hardware-waveform state within the PWM core storage limit - clarify that either co-packaged AC200 or AC300 PHY uses the bypass clock - drop Tested-by tags after the clock implementation was reworked - quantize inverted waveforms in hardware ticks and report their physical offset - force released channels off so stale waveforms cannot restart with a sibling - track the active and pending cycle for the period-update handshake and adapt the sleep interval to bound MMIO polling on long periods - quiesce rate, polarity and bypass transitions before reprogramming the output - clear unsupported pulse mode when programming periodic waveforms - preserve firmware-active outputs through generic unused-clock cleanup, stop any still-unclaimed channels at sync_state(), reconcile empty pair gates through CCF, and reject pair retuning while either output is active - hold pair-rate exclusivity only while an output is active - make pair-rate changes transactional and restore a quiesced output on failure - clear dormant dead-zone state when both pair outputs are disabled, while rejecting active dead-zone and pulse modes which the PWM API cannot represent - correct the hardware topology to show the pair gate before div_m - remove unused register definitions and simplify the fixed parent topology --- drivers/pwm/Kconfig | 12 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-sun8i.c | 1542 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1555 insertions(+) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index e8886a9b64d9..e7fd2e66b1aa 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -748,6 +748,18 @@ config PWM_SUN4I To compile this driver as a module, choose M here: the module will be called pwm-sun4i. +config PWM_SUN8I + tristate "Allwinner sun8i/sun50i PWM support" + depends on ARCH_SUNXI || COMPILE_TEST + depends on HAS_IOMEM && COMMON_CLK + help + PWM framework driver for Allwinner controllers whose channels share + paired source clocks. In addition to PWM operation, each channel's + hardware bypass path can be exported as a clock output. + + To compile this driver as a module, choose M here: the module + will be called pwm-sun8i. + config PWM_SUNPLUS tristate "Sunplus PWM support" depends on ARCH_SUNPLUS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5630a521a7cf..9c922b1fd0b4 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o +obj-$(CONFIG_PWM_SUN8I) += pwm-sun8i.o obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TH1520) += pwm_th1520.o diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c new file mode 100644 index 000000000000..5c3580a0924d --- /dev/null +++ b/drivers/pwm/pwm-sun8i.c @@ -0,0 +1,1542 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Allwinner sun8i Pulse Width Modulation Controller + * + * (C) Copyright 2025 Richard Genoud, Bootlin + * (C) Copyright 2026 James Hilliard + * + * Based on drivers/pwm/pwm-sun4i.c with Copyright: + * + * Copyright (C) 2014 Alexandre Belloni + * + * Channels are paired (0/1, 2/3, 4/5). Each pair shares a clock source and + * first prescaler (div_m), while each channel has its own second prescaler + * (div_k) and bypass path. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PWMCC Pairs Clock Configuration Registers */ +#define SUN8I_PWM_PCCR(pair) (0x20 + ((pair) * 0x4)) +#define SUN8I_PWM_PCCR_SRC_SHIFT 7 +#define SUN8I_PWM_PCCR_SRC_MASK GENMASK(1, 0) +#define SUN8I_PWM_PCCR_GATE_BIT 4 +#define SUN8I_PWM_PCCR_GATE BIT(SUN8I_PWM_PCCR_GATE_BIT) +#define SUN8I_PWM_PCCR_BYPASS_BIT(chan) ((chan) % 2 + 5) +#define SUN8I_PWM_PCCR_DIV_M_SHIFT 0 +#define SUN8I_PWM_PCCR_DIV_M_WIDTH 4 + +/* PWM Enable Register */ +#define SUN8I_PWM_PER 0x40 +#define SUN8I_PWM_ENABLE(chan) BIT(chan) + +/* PWM Control Register */ +#define SUN8I_PWM_PCR(chan) (0x60 + (chan) * 0x20) +#define SUN8I_PWM_PCR_PRESCAL_K_MASK GENMASK(7, 0) +#define SUN8I_PWM_PCR_ACTIVE_STATE BIT(8) +#define SUN8I_PWM_PCR_MODE BIT(9) +#define SUN8I_PWM_PCR_PULSE_START BIT(10) +#define SUN8I_PWM_PCR_PERIOD_READY BIT(11) /* 0: ready, 1: busy */ + +/* PWM Period Register */ +#define SUN8I_PWM_PPR(chan) (0x64 + (chan) * 0x20) +#define SUN8I_PWM_PPR_PERIOD_MASK GENMASK(31, 16) +#define SUN8I_PWM_PPR_DUTY_MASK GENMASK(15, 0) +#define SUN8I_PWM_PPR_PERIOD_VALUE(reg) (FIELD_GET(SUN8I_PWM_PPR_PERIOD_MASK, reg) + 1) +#define SUN8I_PWM_PPR_DUTY_VALUE(reg) FIELD_GET(SUN8I_PWM_PPR_DUTY_MASK, reg) +#define SUN8I_PWM_PPR_PERIOD(prd) FIELD_PREP(SUN8I_PWM_PPR_PERIOD_MASK, (prd) - 1) +#define SUN8I_PWM_PPR_DUTY(dty) FIELD_PREP(SUN8I_PWM_PPR_DUTY_MASK, dty) +#define SUN8I_PWM_PPR_PERIOD_MAX (FIELD_MAX(SUN8I_PWM_PPR_PERIOD_MASK) + 1) + +/* PWM pair dead-zone control registers. */ +#define SUN8I_PWM_PDZCR(pair) (0x30 + ((pair) * 0x4)) +#define SUN8I_PWM_PDZCR_ENABLE BIT(0) + +#define SUN8I_PWM_PERIOD_READY_MARGIN_US 50 +#define SUN8I_PWM_PERIOD_READY_MIN_POLL_US 10 +#define SUN8I_PWM_PERIOD_READY_MAX_POLL_US 10000 +#define SUN8I_PWM_PERIOD_READY_POLL_COUNT 1024 + +#define SUN8I_PWM_PAIR_IDX(chan) ((chan) >> 1) + +/* + * Block diagram of the PWM clock controller: + * + * _____ ______ ________ + * OSC24M --->| | | | | | + * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> SUN8I_PWM_clock_src_xy + * |_____| |______| |________| + * ________ + * | | + * +->| /div_k |---> SUN8I_PWM_clock_x + * | |________| + * | ______ + * | | | + * +-->| Gate |----> SUN8I_PWM_bypass_clock_x + * | |______| + * SUN8I_PWM_clock_src_xy ---+ ________ + * | | | + * +->| /div_k |---> SUN8I_PWM_clock_y + * | |________| + * | ______ + * | | | + * +-->| Gate |----> SUN8I_PWM_bypass_clock_y + * |______| + * + * NB: when the bypass is set, all the PWM logic is bypassed. + * So, the duty cycle and polarity can't be modified (we just have a clock). + * The bypass in PWM mode is used to achieve a 1/2 relative duty cycle with the + * fastest clock. + * + * SUN8I_PWM_clock_x/y serve for the PWM purpose. + * SUN8I_PWM_bypass_clock_x/y serve for the clock-provider purpose. + * + */ + +/* /div_m is a power-of-two divider limited to /256. */ +static const struct clk_div_table sun8i_pwm_div_m_table[] = { + { .val = 0, .div = 1 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 8 }, + { .val = 4, .div = 16 }, + { .val = 5, .div = 32 }, + { .val = 6, .div = 64 }, + { .val = 7, .div = 128 }, + { .val = 8, .div = 256 }, + { /* sentinel */ } +}; + +enum sun8i_pwm_mode { + SUN8I_PWM_MODE_NONE, + SUN8I_PWM_MODE_PWM, + SUN8I_PWM_MODE_CLK, +}; + +struct sun8i_pwm_data { + unsigned int npwm; +}; + +struct sun8i_pwm_chip; + +struct sun8i_pwm_pair { + struct clk_mux mux; + struct clk_hw gate_hw; + struct clk_divider divider; + struct clk_hw *hw; + struct notifier_block rate_nb; + struct sun8i_pwm_chip *chip; + unsigned int index; +}; + +struct sun8i_pwm_bypass { + struct clk_hw hw; + struct sun8i_pwm_chip *chip; + unsigned int index; +}; + +struct sun8i_pwm_channel { + struct sun8i_pwm_bypass bypass; + struct clk *pair_clk; + enum sun8i_pwm_mode mode; + u64 pending_period_ns; + bool rate_exclusive; +}; + +struct sun8i_pwm_chip { + struct clk_hw_onecell_data *hw_data; + struct sun8i_pwm_pair *pairs; + struct sun8i_pwm_channel *channels; + struct clk *bus_clk; + void __iomem *base; + const struct sun8i_pwm_data *data; + /* Protects shared registers and channel ownership. */ + spinlock_t lock; +}; + +struct sun8i_pwm_waveform { + u32 duty_ticks; + u32 period_ticks; + u32 pair_rate; + u16 div_k; + u8 enabled:1; + u8 active_state:1; + u8 bypass_en:1; +}; + +static inline struct sun8i_pwm_chip *sun8i_pwm_from_chip(const struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline u32 sun8i_pwm_readl(struct sun8i_pwm_chip *sun8i_chip, + unsigned long offset) +{ + return readl(sun8i_chip->base + offset); +} + +static inline void sun8i_pwm_writel(struct sun8i_pwm_chip *sun8i_chip, + u32 val, unsigned long offset) +{ + writel(val, sun8i_chip->base + offset); +} + +static void sun8i_pwm_set_bypass_locked(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx, bool enable) +{ + unsigned long reg_offset; + u32 val; + + lockdep_assert_held(&sun8i_chip->lock); + + reg_offset = SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(idx)); + val = sun8i_pwm_readl(sun8i_chip, reg_offset); + if (enable) + val |= BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx)); + else + val &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx)); + + sun8i_pwm_writel(sun8i_chip, val, reg_offset); +} + +static void sun8i_pwm_set_enabled_locked(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx, bool enable) +{ + u32 val; + + lockdep_assert_held(&sun8i_chip->lock); + + val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + if (enable) + val |= SUN8I_PWM_ENABLE(idx); + else + val &= ~SUN8I_PWM_ENABLE(idx); + sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PER); +} + +static bool +sun8i_pwm_channel_is_enabled_locked(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx) +{ + unsigned int pair = SUN8I_PWM_PAIR_IDX(idx); + u32 pccr, per; + + lockdep_assert_held(&sun8i_chip->lock); + + pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + + return (pccr & SUN8I_PWM_PCCR_GATE) && + (per & SUN8I_PWM_ENABLE(idx)); +} + +static u32 sun8i_pwm_pair_enable_mask(struct sun8i_pwm_chip *sun8i_chip, + unsigned int pair) +{ + unsigned int first = pair * 2; + u32 mask = SUN8I_PWM_ENABLE(first); + + if (first + 1 < sun8i_chip->data->npwm) + mask |= SUN8I_PWM_ENABLE(first + 1); + + return mask; +} + +static u32 +sun8i_pwm_pair_owner_mask_locked(struct sun8i_pwm_chip *sun8i_chip, + unsigned int pair) +{ + unsigned int first = pair * 2; + u32 mask = 0; + + lockdep_assert_held(&sun8i_chip->lock); + + if (sun8i_chip->channels[first].mode != SUN8I_PWM_MODE_NONE) + mask |= SUN8I_PWM_ENABLE(first); + if (first + 1 < sun8i_chip->data->npwm && + sun8i_chip->channels[first + 1].mode != SUN8I_PWM_MODE_NONE) + mask |= SUN8I_PWM_ENABLE(first + 1); + + return mask; +} + +static inline struct sun8i_pwm_pair * +sun8i_pwm_pair_from_gate_hw(struct clk_hw *hw) +{ + return container_of(hw, struct sun8i_pwm_pair, gate_hw); +} + +static int sun8i_pwm_pair_gate_enable(struct clk_hw *hw) +{ + struct sun8i_pwm_pair *pair = sun8i_pwm_pair_from_gate_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = pair->chip; + unsigned long flags; + u32 pccr; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair->index)); + pccr |= SUN8I_PWM_PCCR_GATE; + sun8i_pwm_writel(sun8i_chip, pccr, SUN8I_PWM_PCCR(pair->index)); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return 0; +} + +static void sun8i_pwm_pair_gate_disable(struct clk_hw *hw) +{ + struct sun8i_pwm_pair *pair = sun8i_pwm_pair_from_gate_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = pair->chip; + unsigned long reg = SUN8I_PWM_PCCR(pair->index); + unsigned long flags; + u32 pccr, per; + + /* CCF counts do not include outputs awaiting firmware-state handoff. */ + spin_lock_irqsave(&sun8i_chip->lock, flags); + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + if (!(per & sun8i_pwm_pair_enable_mask(sun8i_chip, pair->index))) { + pccr = sun8i_pwm_readl(sun8i_chip, reg); + pccr &= ~SUN8I_PWM_PCCR_GATE; + sun8i_pwm_writel(sun8i_chip, pccr, reg); + } + spin_unlock_irqrestore(&sun8i_chip->lock, flags); +} + +static int sun8i_pwm_pair_gate_is_enabled(struct clk_hw *hw) +{ + struct sun8i_pwm_pair *pair = sun8i_pwm_pair_from_gate_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = pair->chip; + unsigned long reg = SUN8I_PWM_PCCR(pair->index); + unsigned long flags; + bool enabled; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + enabled = sun8i_pwm_readl(sun8i_chip, reg) & SUN8I_PWM_PCCR_GATE; + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return enabled; +} + +static const struct clk_ops sun8i_pwm_pair_gate_ops = { + .enable = sun8i_pwm_pair_gate_enable, + .disable = sun8i_pwm_pair_gate_disable, + .is_enabled = sun8i_pwm_pair_gate_is_enabled, +}; + +static int sun8i_pwm_pair_rate_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct sun8i_pwm_pair *pair = + container_of(nb, struct sun8i_pwm_pair, rate_nb); + struct sun8i_pwm_chip *sun8i_chip = pair->chip; + unsigned long flags; + bool active; + + if (event != PRE_RATE_CHANGE) + return NOTIFY_DONE; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + active = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER) & + sun8i_pwm_pair_enable_mask(sun8i_chip, pair->index); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return active ? NOTIFY_BAD : NOTIFY_OK; +} + +static inline struct sun8i_pwm_bypass * +sun8i_pwm_bypass_from_hw(struct clk_hw *hw) +{ + return container_of(hw, struct sun8i_pwm_bypass, hw); +} + +static int sun8i_pwm_bypass_prepare(struct clk_hw *hw) +{ + struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = bypass->chip; + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index]; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + if (chan->mode != SUN8I_PWM_MODE_NONE) + ret = -EBUSY; + else + chan->mode = SUN8I_PWM_MODE_CLK; + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return ret; +} + +static void sun8i_pwm_bypass_unprepare(struct clk_hw *hw) +{ + struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = bypass->chip; + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index]; + unsigned long flags; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_CLK)) + goto out_unlock; + + chan->mode = SUN8I_PWM_MODE_NONE; + +out_unlock: + spin_unlock_irqrestore(&sun8i_chip->lock, flags); +} + +static int sun8i_pwm_bypass_enable(struct clk_hw *hw) +{ + struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = bypass->chip; + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index]; + unsigned int pair = SUN8I_PWM_PAIR_IDX(bypass->index); + unsigned long flags; + u32 pccr, per; + int ret = 0; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_CLK)) { + ret = -EBUSY; + goto out_unlock; + } + + pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + if (!(pccr & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(bypass->index))) || + !(per & SUN8I_PWM_ENABLE(bypass->index))) { + sun8i_pwm_set_enabled_locked(sun8i_chip, bypass->index, false); + sun8i_pwm_set_bypass_locked(sun8i_chip, bypass->index, true); + sun8i_pwm_set_enabled_locked(sun8i_chip, bypass->index, true); + } + +out_unlock: + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return ret; +} + +static void sun8i_pwm_bypass_disable(struct clk_hw *hw) +{ + struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = bypass->chip; + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[bypass->index]; + unsigned long flags; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_CLK)) + goto out_unlock; + + sun8i_pwm_set_enabled_locked(sun8i_chip, bypass->index, false); + sun8i_pwm_set_bypass_locked(sun8i_chip, bypass->index, false); + +out_unlock: + spin_unlock_irqrestore(&sun8i_chip->lock, flags); +} + +static void sun8i_pwm_bypass_disable_unused(struct clk_hw *hw) +{ + /* Defer inherited-output cleanup to sun8i_pwm_sync_state(). */ +} + +static int sun8i_pwm_bypass_is_enabled(struct clk_hw *hw) +{ + struct sun8i_pwm_bypass *bypass = sun8i_pwm_bypass_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = bypass->chip; + unsigned int pair = SUN8I_PWM_PAIR_IDX(bypass->index); + unsigned long flags; + bool enabled; + u32 val; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + + val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + enabled = (val & SUN8I_PWM_PCCR_GATE) && + (val & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(bypass->index))); + + val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + enabled = enabled && (val & SUN8I_PWM_ENABLE(bypass->index)); + + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return enabled; +} + +static unsigned long sun8i_pwm_bypass_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return parent_rate; +} + +static const struct clk_ops sun8i_pwm_bypass_ops = { + .prepare = sun8i_pwm_bypass_prepare, + .unprepare = sun8i_pwm_bypass_unprepare, + .enable = sun8i_pwm_bypass_enable, + .disable = sun8i_pwm_bypass_disable, + .disable_unused = sun8i_pwm_bypass_disable_unused, + .is_enabled = sun8i_pwm_bypass_is_enabled, + .recalc_rate = sun8i_pwm_bypass_recalc_rate, +}; + +static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip); + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm]; + unsigned int idx = pwm->hwpwm; + unsigned long flags; + bool was_enabled = false; + int ret; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + if (chan->mode != SUN8I_PWM_MODE_NONE) { + ret = -EBUSY; + } else { + was_enabled = + sun8i_pwm_channel_is_enabled_locked(sun8i_chip, idx); + chan->mode = SUN8I_PWM_MODE_PWM; + ret = 0; + } + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + if (ret) + return ret; + + if (was_enabled) { + ret = clk_rate_exclusive_get(chan->pair_clk); + if (ret) + goto err_release_channel; + WRITE_ONCE(chan->rate_exclusive, true); + } + + ret = clk_prepare_enable(chan->pair_clk); + if (ret) { + if (READ_ONCE(chan->rate_exclusive)) { + clk_rate_exclusive_put(chan->pair_clk); + WRITE_ONCE(chan->rate_exclusive, false); + } + goto err_release_channel; + } + + return 0; + +err_release_channel: + spin_lock_irqsave(&sun8i_chip->lock, flags); + chan->mode = SUN8I_PWM_MODE_NONE; + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return ret; +} + +static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip); + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm]; + unsigned long flags; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + if (WARN_ON_ONCE(chan->mode != SUN8I_PWM_MODE_PWM)) { + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + return; + } + + sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false); + sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + if (READ_ONCE(chan->rate_exclusive)) { + clk_rate_exclusive_put(chan->pair_clk); + WRITE_ONCE(chan->rate_exclusive, false); + } + clk_disable_unprepare(chan->pair_clk); + + spin_lock_irqsave(&sun8i_chip->lock, flags); + chan->mode = SUN8I_PWM_MODE_NONE; + spin_unlock_irqrestore(&sun8i_chip->lock, flags); +} + +static int sun8i_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct sun8i_pwm_waveform *wfhw = _wfhw; + struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip); + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm]; + unsigned int pair = SUN8I_PWM_PAIR_IDX(pwm->hwpwm); + unsigned long flags, pair_rate; + u32 pccr, pcr, pdzcr, per, ppr; + + pair_rate = clk_get_rate(chan->pair_clk); + if (pair_rate > U32_MAX) + return -ERANGE; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + pcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm)); + ppr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PPR(pwm->hwpwm)); + pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair)); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + *wfhw = (struct sun8i_pwm_waveform) { + .enabled = !!(pccr & SUN8I_PWM_PCCR_GATE) && + !!(per & SUN8I_PWM_ENABLE(pwm->hwpwm)), + .bypass_en = !!(pccr & + BIT(SUN8I_PWM_PCCR_BYPASS_BIT(pwm->hwpwm))), + .active_state = !!(pcr & SUN8I_PWM_PCR_ACTIVE_STATE), + .duty_ticks = SUN8I_PWM_PPR_DUTY_VALUE(ppr), + .period_ticks = SUN8I_PWM_PPR_PERIOD_VALUE(ppr), + .pair_rate = pair_rate, + .div_k = FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, pcr) + 1, + }; + + if (wfhw->enabled && !wfhw->bypass_en && + (pdzcr & SUN8I_PWM_PDZCR_ENABLE)) + return -EOPNOTSUPP; + if (wfhw->enabled && !wfhw->bypass_en && + (pcr & SUN8I_PWM_PCR_MODE)) + return -EOPNOTSUPP; + + return 0; +} + +static u64 sun8i_pwm_ticks_to_ns(u32 ticks, u16 div_k, u32 pair_rate) +{ + return DIV_ROUND_UP_ULL(NSEC_PER_SEC * (u64)ticks * div_k, + pair_rate); +} + +static void +sun8i_pwm_waveform_to_ns(const struct sun8i_pwm_waveform *wfhw, + struct pwm_waveform *wf) +{ + u32 pair_rate = wfhw->pair_rate; + u16 div_k = wfhw->div_k; + + wf->duty_offset_ns = 0; + + if (!wfhw->enabled || !pair_rate) { + wf->period_length_ns = 0; + wf->duty_length_ns = 0; + return; + } + + if (wfhw->bypass_en) { + wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, + pair_rate); + wf->duty_length_ns = + DIV_ROUND_UP_ULL(NSEC_PER_SEC, + 2ULL * pair_rate); + } else { + wf->period_length_ns = sun8i_pwm_ticks_to_ns(wfhw->period_ticks, + div_k, pair_rate); + if (wfhw->active_state) { + u32 duty_ticks = min(wfhw->duty_ticks, + wfhw->period_ticks); + + wf->duty_length_ns = + sun8i_pwm_ticks_to_ns(duty_ticks, div_k, pair_rate); + } else if (!wfhw->duty_ticks) { + wf->duty_length_ns = wf->period_length_ns; + } else if (wfhw->duty_ticks >= wfhw->period_ticks) { + wf->duty_length_ns = 0; + } else { + u32 low_ticks = wfhw->duty_ticks; + u32 high_ticks = wfhw->period_ticks - wfhw->duty_ticks; + + wf->duty_length_ns = + sun8i_pwm_ticks_to_ns(high_ticks, div_k, pair_rate); + wf->duty_offset_ns = + sun8i_pwm_ticks_to_ns(low_ticks, div_k, pair_rate); + } + } +} + +static int sun8i_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + const struct sun8i_pwm_waveform *wfhw = _wfhw; + + sun8i_pwm_waveform_to_ns(wfhw, wf); + + dev_dbg(pwmchip_parent(chip), + "pwm#%u: pair-rate=%u, div-k=%u, period=%u, duty=%u, bypass=%u, active-high=%u -> %llu/%llu [+%llu]\n", + pwm->hwpwm, wfhw->pair_rate, wfhw->div_k, + wfhw->period_ticks, wfhw->duty_ticks, wfhw->bypass_en, + wfhw->active_state, + wf->duty_length_ns, wf->period_length_ns, + wf->duty_offset_ns); + + return 0; +} + +static bool +sun8i_pwm_pair_rate_constrained(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx) +{ + unsigned int sibling = idx ^ 1; + unsigned long flags; + bool constrained; + + if (sibling >= sun8i_chip->data->npwm) + return false; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + constrained = sun8i_chip->channels[sibling].mode == + SUN8I_PWM_MODE_CLK || + READ_ONCE(sun8i_chip->channels[sibling].rate_exclusive) || + sun8i_pwm_channel_is_enabled_locked(sun8i_chip, + sibling); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return constrained; +} + +static int sun8i_pwm_clear_idle_deadzone(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx) +{ + unsigned int pair = SUN8I_PWM_PAIR_IDX(idx); + unsigned long flags; + u32 pdzcr, per; + int ret = 0; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair)); + if (!(pdzcr & SUN8I_PWM_PDZCR_ENABLE)) + goto out_unlock; + + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + if (per & sun8i_pwm_pair_enable_mask(sun8i_chip, pair)) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + + pdzcr &= ~SUN8I_PWM_PDZCR_ENABLE; + sun8i_pwm_writel(sun8i_chip, pdzcr, SUN8I_PWM_PDZCR(pair)); + +out_unlock: + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return ret; +} + +static u64 sun8i_pwm_max_period_ns(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx) +{ + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[idx]; + unsigned long flags, pair_rate; + u32 pcr; + u16 div_k; + + pair_rate = clk_get_rate(chan->pair_clk); + if (!pair_rate || pair_rate > U32_MAX) + return 0; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + pcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(idx)); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + div_k = FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, pcr) + 1; + + return sun8i_pwm_ticks_to_ns(SUN8I_PWM_PPR_PERIOD_MAX, div_k, + pair_rate); +} + +static int sun8i_pwm_wait_period_ready(struct sun8i_pwm_chip *sun8i_chip, + unsigned int idx, u64 *timeout_us) +{ + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[idx]; + unsigned long poll_us; + u64 period_ns; + u32 val; + int ret; + + /* PPR contains the pending value, not necessarily the active period. */ + period_ns = chan->pending_period_ns; + if (!period_ns) + period_ns = sun8i_pwm_max_period_ns(sun8i_chip, idx); + *timeout_us = DIV_ROUND_UP_ULL(period_ns, NSEC_PER_USEC) + + SUN8I_PWM_PERIOD_READY_MARGIN_US; + poll_us = clamp_t(u64, + DIV_ROUND_UP_ULL(*timeout_us, + SUN8I_PWM_PERIOD_READY_POLL_COUNT), + SUN8I_PWM_PERIOD_READY_MIN_POLL_US, + SUN8I_PWM_PERIOD_READY_MAX_POLL_US); + + ret = readl_poll_timeout(sun8i_chip->base + SUN8I_PWM_PCR(idx), val, + !(val & SUN8I_PWM_PCR_PERIOD_READY), poll_us, + *timeout_us); + if (!ret) + chan->pending_period_ns = 0; + + return ret; +} + +static int sun8i_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, const void *_wfhw) +{ + const struct sun8i_pwm_waveform *wfhw = _wfhw; + struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip); + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm]; + struct device *dev = pwmchip_parent(chip); + unsigned long bus_rate, current_rate, restored_rate; + unsigned long flags; + u64 new_ns, old_ns, timeout_us; + bool acquired_exclusive = false; + bool can_restore_enable = true; + bool current_bypass, needs_quiesce, was_enabled; + u16 current_div_k; + u32 old_ticks, pcr, ppr, val; + int restore_ret, ret; + + if (!wfhw->enabled) { + spin_lock_irqsave(&sun8i_chip->lock, flags); + sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false); + sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + if (READ_ONCE(chan->rate_exclusive)) { + clk_rate_exclusive_put(chan->pair_clk); + WRITE_ONCE(chan->rate_exclusive, false); + } + + return 0; + } + + if (!wfhw->pair_rate || + (!wfhw->bypass_en && + (!wfhw->div_k || wfhw->div_k > 256 || + !wfhw->period_ticks || + wfhw->period_ticks > SUN8I_PWM_PPR_PERIOD_MAX || + wfhw->duty_ticks > FIELD_MAX(SUN8I_PWM_PPR_DUTY_MASK)))) + return -EINVAL; + + if (!wfhw->bypass_en) { + ret = sun8i_pwm_clear_idle_deadzone(sun8i_chip, pwm->hwpwm); + if (ret) + return ret; + } + + current_rate = clk_get_rate(chan->pair_clk); + if (!current_rate || current_rate > U32_MAX) + return -ERANGE; + bus_rate = clk_get_rate(sun8i_chip->bus_clk); + + if (!wfhw->bypass_en) { + /* Do not overwrite a PPR update which has not latched yet. */ + ret = sun8i_pwm_wait_period_ready(sun8i_chip, pwm->hwpwm, + &timeout_us); + if (ret) { + dev_err_ratelimited(dev, "pwm#%u: update timeout after %llu us\n", + pwm->hwpwm, timeout_us); + return ret; + } + } + + spin_lock_irqsave(&sun8i_chip->lock, flags); + was_enabled = sun8i_pwm_channel_is_enabled_locked(sun8i_chip, + pwm->hwpwm); + val = sun8i_pwm_readl(sun8i_chip, + SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(pwm->hwpwm))); + current_bypass = !!(val & + BIT(SUN8I_PWM_PCCR_BYPASS_BIT(pwm->hwpwm))); + pcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm)); + ppr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PPR(pwm->hwpwm)); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + current_div_k = FIELD_GET(SUN8I_PWM_PCR_PRESCAL_K_MASK, pcr) + 1; + + if (current_rate != wfhw->pair_rate && + sun8i_pwm_pair_rate_constrained(sun8i_chip, pwm->hwpwm)) + return -EBUSY; + + if (was_enabled && !READ_ONCE(chan->rate_exclusive)) { + ret = clk_rate_exclusive_get(chan->pair_clk); + if (ret) + return ret; + WRITE_ONCE(chan->rate_exclusive, true); + } + + /* + * Updating PPR while running is safe only if the input clocks and + * polarity remain unchanged and PCLK is faster than the PWM clock. + * Otherwise stop the channel before touching its configuration. + */ + needs_quiesce = was_enabled && + (current_bypass != wfhw->bypass_en || + current_rate != wfhw->pair_rate || + (!wfhw->bypass_en && + (!!(pcr & SUN8I_PWM_PCR_ACTIVE_STATE) != + wfhw->active_state || + current_div_k != wfhw->div_k || + (pcr & SUN8I_PWM_PCR_MODE) || + !bus_rate || + bus_rate <= DIV_ROUND_UP_ULL(wfhw->pair_rate, + wfhw->div_k)))); + + if (needs_quiesce) { + spin_lock_irqsave(&sun8i_chip->lock, flags); + sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + } + + if (!was_enabled && !READ_ONCE(chan->rate_exclusive)) { + if (current_rate == wfhw->pair_rate) + ret = clk_rate_exclusive_get(chan->pair_clk); + else + ret = clk_set_rate_exclusive(chan->pair_clk, + wfhw->pair_rate); + if (ret) + return ret; + WRITE_ONCE(chan->rate_exclusive, true); + acquired_exclusive = true; + } else if (current_rate != wfhw->pair_rate) { + ret = clk_set_rate(chan->pair_clk, wfhw->pair_rate); + if (ret) + goto restore_enable; + } + + if (clk_get_rate(chan->pair_clk) != wfhw->pair_rate) { + ret = -EINVAL; + goto restore_rate; + } + + if (!wfhw->bypass_en) { + /* Return the channel to cycle mode without replaying W1S bits. */ + pcr &= ~(SUN8I_PWM_PCR_PRESCAL_K_MASK | + SUN8I_PWM_PCR_ACTIVE_STATE | SUN8I_PWM_PCR_MODE | + SUN8I_PWM_PCR_PULSE_START | + SUN8I_PWM_PCR_PERIOD_READY); + pcr |= FIELD_PREP(SUN8I_PWM_PCR_PRESCAL_K_MASK, + wfhw->div_k - 1); + if (wfhw->active_state) + pcr |= SUN8I_PWM_PCR_ACTIVE_STATE; + sun8i_pwm_writel(sun8i_chip, pcr, SUN8I_PWM_PCR(pwm->hwpwm)); + + val = SUN8I_PWM_PPR_DUTY(wfhw->duty_ticks); + val |= SUN8I_PWM_PPR_PERIOD(wfhw->period_ticks); + old_ns = 0; + if (was_enabled && !current_bypass) { + old_ticks = SUN8I_PWM_PPR_PERIOD_VALUE(ppr); + old_ns = sun8i_pwm_ticks_to_ns(old_ticks, current_div_k, + current_rate); + } + new_ns = sun8i_pwm_ticks_to_ns(wfhw->period_ticks, + wfhw->div_k, wfhw->pair_rate); + chan->pending_period_ns = max(old_ns, new_ns); + sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PPR(pwm->hwpwm)); + } + + spin_lock_irqsave(&sun8i_chip->lock, flags); + sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, wfhw->bypass_en); + sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, true); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + return 0; + +restore_rate: + if (current_rate != wfhw->pair_rate) { + restore_ret = clk_set_rate(chan->pair_clk, current_rate); + restored_rate = clk_get_rate(chan->pair_clk); + if (restore_ret || restored_rate != current_rate) { + dev_err(dev, + "pwm#%u: failed to restore pair clock rate %lu: %d (got %lu)\n", + pwm->hwpwm, current_rate, restore_ret, + restored_rate); + can_restore_enable = false; + } + } + if (acquired_exclusive) { + clk_rate_exclusive_put(chan->pair_clk); + WRITE_ONCE(chan->rate_exclusive, false); + } +restore_enable: + if (needs_quiesce && can_restore_enable) { + spin_lock_irqsave(&sun8i_chip->lock, flags); + sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, true); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + } + + return ret; +} + +struct sun8i_pwm_rounding { + const struct pwm_waveform *requested; + struct sun8i_pwm_waveform best; + struct pwm_waveform best_wf; + u32 current_pair_rate; + bool have_best; +}; + +static bool +sun8i_pwm_candidate_is_better(const struct sun8i_pwm_rounding *rounding, + const struct sun8i_pwm_waveform *candidate, + const struct pwm_waveform *candidate_wf) +{ + const struct pwm_waveform *requested = rounding->requested; + bool candidate_period_down, best_period_down; + + /* A zero-duty, zero-offset cycle-mode candidate always exists. */ + if (candidate_wf->duty_length_ns > requested->duty_length_ns || + candidate_wf->duty_offset_ns > requested->duty_offset_ns) + return false; + + if (!rounding->have_best) + return true; + + candidate_period_down = + candidate_wf->period_length_ns <= requested->period_length_ns; + best_period_down = + rounding->best_wf.period_length_ns <= requested->period_length_ns; + if (candidate_period_down != best_period_down) + return candidate_period_down; + + if (candidate_wf->period_length_ns != + rounding->best_wf.period_length_ns) { + if (candidate_period_down) + return candidate_wf->period_length_ns > + rounding->best_wf.period_length_ns; + + return candidate_wf->period_length_ns < + rounding->best_wf.period_length_ns; + } + + if (candidate_wf->duty_length_ns != + rounding->best_wf.duty_length_ns) + return candidate_wf->duty_length_ns > + rounding->best_wf.duty_length_ns; + + if (candidate_wf->duty_offset_ns != + rounding->best_wf.duty_offset_ns) + return candidate_wf->duty_offset_ns > + rounding->best_wf.duty_offset_ns; + + /* Avoid a shared pair-rate change when two settings are equivalent. */ + return candidate->pair_rate == rounding->current_pair_rate && + rounding->best.pair_rate != rounding->current_pair_rate; +} + +static void +sun8i_pwm_consider_candidate(struct sun8i_pwm_rounding *rounding, + const struct sun8i_pwm_waveform *candidate) +{ + struct pwm_waveform candidate_wf; + + sun8i_pwm_waveform_to_ns(candidate, &candidate_wf); + if (!sun8i_pwm_candidate_is_better(rounding, candidate, + &candidate_wf)) + return; + + rounding->best = *candidate; + rounding->best_wf = candidate_wf; + rounding->have_best = true; +} + +static void +sun8i_pwm_consider_normal(struct sun8i_pwm_rounding *rounding, u32 pair_rate, + u16 div_k) +{ + const struct pwm_waveform *requested = rounding->requested; + struct sun8i_pwm_waveform candidate = { + .enabled = true, + .pair_rate = pair_rate, + .div_k = div_k, + }; + u64 denominator = NSEC_PER_SEC * (u64)div_k; + u64 duty_ticks, period_ticks; + u64 duty_ns = requested->duty_length_ns; + u64 period_ns = requested->period_length_ns; + u32 inactive_ticks; + + period_ticks = mul_u64_u64_div_u64(period_ns, pair_rate, denominator); + period_ticks = clamp_t(u64, period_ticks, 1, SUN8I_PWM_PPR_PERIOD_MAX); + candidate.period_ticks = period_ticks; + if (candidate.period_ticks > 1 && + sun8i_pwm_ticks_to_ns(candidate.period_ticks, div_k, pair_rate) > + requested->period_length_ns) + candidate.period_ticks--; + + duty_ticks = mul_u64_u64_div_u64(duty_ns, pair_rate, denominator); + duty_ticks = min_t(u64, duty_ticks, candidate.period_ticks); + if (duty_ticks && + sun8i_pwm_ticks_to_ns(duty_ticks, div_k, pair_rate) > + requested->duty_length_ns) + duty_ticks--; + + /* + * Zero active ticks encode either constant level. For a toggling + * waveform, active-low mode places the rising edge after the inactive + * part, which is the only non-zero offset supported by the hardware. + */ + if (!duty_ticks) { + candidate.active_state = true; + candidate.duty_ticks = 0; + } else if (duty_ticks == candidate.period_ticks) { + candidate.active_state = false; + candidate.duty_ticks = 0; + } else { + inactive_ticks = candidate.period_ticks - duty_ticks; + if (sun8i_pwm_ticks_to_ns(inactive_ticks, div_k, pair_rate) <= + requested->duty_offset_ns) { + candidate.active_state = false; + candidate.duty_ticks = inactive_ticks; + } else { + candidate.active_state = true; + candidate.duty_ticks = duty_ticks; + } + } + + sun8i_pwm_consider_candidate(rounding, &candidate); +} + +static void +sun8i_pwm_consider_pair_rate(struct sun8i_pwm_rounding *rounding, + u32 pair_rate) +{ + struct sun8i_pwm_waveform bypass = { + .duty_ticks = 1, + .period_ticks = 1, + .pair_rate = pair_rate, + .div_k = 1, + .enabled = true, + .active_state = true, + .bypass_en = true, + }; + + for (unsigned int div_k = 1; div_k <= 256; div_k++) + sun8i_pwm_consider_normal(rounding, pair_rate, div_k); + + sun8i_pwm_consider_candidate(rounding, &bypass); +} + +static int sun8i_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip); + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm]; + struct sun8i_pwm_pair *pair = + &sun8i_chip->pairs[SUN8I_PWM_PAIR_IDX(pwm->hwpwm)]; + struct sun8i_pwm_waveform *wfhw = _wfhw; + struct sun8i_pwm_rounding rounding = { + .requested = wf, + }; + unsigned long current_rate; + + if (!wf->period_length_ns) { + *wfhw = (struct sun8i_pwm_waveform) { + .enabled = false, + }; + return 0; + } + + current_rate = clk_get_rate(chan->pair_clk); + if (!current_rate || current_rate > U32_MAX) + return -ERANGE; + rounding.current_pair_rate = current_rate; + + if (sun8i_pwm_pair_rate_constrained(sun8i_chip, pwm->hwpwm)) { + sun8i_pwm_consider_pair_rate(&rounding, current_rate); + } else { + for (unsigned int parent_idx = 0; + parent_idx < clk_hw_get_num_parents(pair->hw); + parent_idx++) { + struct clk_hw *parent; + unsigned long parent_rate; + + parent = clk_hw_get_parent_by_index(pair->hw, parent_idx); + if (!parent) + continue; + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + continue; + + for (unsigned int i = 0; sun8i_pwm_div_m_table[i].div; + i++) { + u16 div_m = sun8i_pwm_div_m_table[i].div; + u64 pair_rate; + + pair_rate = DIV_ROUND_UP_ULL(parent_rate, div_m); + if (!pair_rate || pair_rate > U32_MAX) + continue; + sun8i_pwm_consider_pair_rate(&rounding, pair_rate); + } + } + } + + if (!rounding.have_best) + return -EINVAL; + *wfhw = rounding.best; + + dev_dbg(pwmchip_parent(chip), + "pwm#%u: %llu/%llu [+%llu] -> pair-rate=%u, div-k=%u, period=%u, duty=%u, bypass=%u, active-high=%u\n", + pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, + wf->duty_offset_ns, wfhw->pair_rate, wfhw->div_k, + wfhw->period_ticks, wfhw->duty_ticks, wfhw->bypass_en, + wfhw->active_state); + + return rounding.best_wf.period_length_ns > wf->period_length_ns; +} + +static const struct pwm_ops sun8i_pwm_ops = { + .request = sun8i_pwm_request, + .free = sun8i_pwm_free, + .sizeof_wfhw = sizeof(struct sun8i_pwm_waveform), + .round_waveform_tohw = sun8i_pwm_round_waveform_tohw, + .round_waveform_fromhw = sun8i_pwm_round_waveform_fromhw, + .read_waveform = sun8i_pwm_read_waveform, + .write_waveform = sun8i_pwm_write_waveform, +}; + +/* Register the shared mux, gate and /div_m clock for each channel pair. */ +static int sun8i_pwm_register_pair_clocks(struct device *dev, + struct sun8i_pwm_chip *sun8i_chip) +{ + static const struct clk_parent_data parent_data[] = { + { .index = 0 }, + { .index = 1 }, + }; + unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2); + + for (unsigned int i = 0; i < num_pairs; i++) { + struct sun8i_pwm_pair *pair = &sun8i_chip->pairs[i]; + void __iomem *reg = sun8i_chip->base + SUN8I_PWM_PCCR(i); + struct clk *pair_clk; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, + "%s#pwm-clk-src%u%u", dev_name(dev), + i * 2, i * 2 + 1); + if (!name) + return -ENOMEM; + + pair->mux.reg = reg; + pair->mux.shift = SUN8I_PWM_PCCR_SRC_SHIFT; + pair->mux.mask = SUN8I_PWM_PCCR_SRC_MASK; + pair->mux.flags = CLK_MUX_ROUND_CLOSEST; + pair->mux.lock = &sun8i_chip->lock; + + pair->chip = sun8i_chip; + pair->index = i; + + pair->divider.reg = reg; + pair->divider.shift = SUN8I_PWM_PCCR_DIV_M_SHIFT; + pair->divider.width = SUN8I_PWM_PCCR_DIV_M_WIDTH; + pair->divider.table = sun8i_pwm_div_m_table; + pair->divider.lock = &sun8i_chip->lock; + + pair->hw = devm_clk_hw_register_composite_pdata(dev, name, + parent_data, + ARRAY_SIZE(parent_data), + &pair->mux.hw, &clk_mux_ops, + &pair->divider.hw, &clk_divider_ops, + &pair->gate_hw, &sun8i_pwm_pair_gate_ops, 0); + if (IS_ERR(pair->hw)) + return dev_err_probe(dev, PTR_ERR(pair->hw), + "Failed to register pair %u clock\n", i); + + pair_clk = devm_clk_hw_get_clk(dev, pair->hw, NULL); + if (IS_ERR(pair_clk)) + return dev_err_probe(dev, PTR_ERR(pair_clk), + "Failed to get pair %u clock\n", i); + + pair->rate_nb.notifier_call = sun8i_pwm_pair_rate_notifier; + ret = devm_clk_notifier_register(dev, pair_clk, &pair->rate_nb); + if (ret) + return dev_err_probe(dev, ret, + "Failed to protect pair %u clock rate\n", i); + } + + return 0; +} + +/* Register a pair-clock consumer and the bypass clock for each channel. */ +static int sun8i_pwm_register_channel_clocks(struct device *dev, + struct sun8i_pwm_chip *sun8i_chip) +{ + for (unsigned int i = 0; i < sun8i_chip->data->npwm; i++) { + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[i]; + struct clk_hw *parent = sun8i_chip->pairs[SUN8I_PWM_PAIR_IDX(i)].hw; + struct clk_parent_data parent_data = { .hw = parent }; + struct clk_init_data init = {}; + const char *name; + int ret; + + /* Separate handles let CCF arbitrate sibling rate exclusivity. */ + chan->pair_clk = devm_clk_hw_get_clk(dev, parent, NULL); + if (IS_ERR(chan->pair_clk)) + return dev_err_probe(dev, PTR_ERR(chan->pair_clk), + "Failed to get pair clock for PWM %u\n", i); + + name = devm_kasprintf(dev, GFP_KERNEL, "%s#pwm-bypass%u", + dev_name(dev), i); + if (!name) + return -ENOMEM; + + chan->bypass.chip = sun8i_chip; + chan->bypass.index = i; + init.name = name; + init.ops = &sun8i_pwm_bypass_ops; + init.parent_data = &parent_data; + init.num_parents = 1; + /* Keep the shared pair rate stable for the prepared lifetime. */ + init.flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE; + chan->bypass.hw.init = &init; + + ret = devm_clk_hw_register(dev, &chan->bypass.hw); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register bypass clock %u\n", i); + + sun8i_chip->hw_data->hws[i] = &chan->bypass.hw; + } + + return 0; +} + +/* + * A disabled pair gate makes any set PER bits ineffective. Clear those stale + * enables before a clock consumer can turn the shared gate on and expose an + * unrequested output. + */ +static void +sun8i_pwm_sanitize_disabled_pairs(struct sun8i_pwm_chip *sun8i_chip) +{ + unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2); + unsigned long flags; + u32 per; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + for (unsigned int pair = 0; pair < num_pairs; pair++) { + unsigned int first = pair * 2; + u32 pccr; + + pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + if (pccr & SUN8I_PWM_PCCR_GATE) + continue; + + per &= ~SUN8I_PWM_ENABLE(first); + pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first)); + if (first + 1 < sun8i_chip->data->npwm) { + per &= ~SUN8I_PWM_ENABLE(first + 1); + pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first + 1)); + } + sun8i_pwm_writel(sun8i_chip, pccr, SUN8I_PWM_PCCR(pair)); + } + sun8i_pwm_writel(sun8i_chip, per, SUN8I_PWM_PER); + spin_unlock_irqrestore(&sun8i_chip->lock, flags); +} + +/* + * Keep firmware-active outputs running until all declared consumers have had + * a chance to claim them. Once the driver core calls sync_state(), any channel + * which still has no PWM or clock owner can be stopped. + */ +static void sun8i_pwm_sync_state(struct device *dev) +{ + struct sun8i_pwm_chip *sun8i_chip = dev_get_drvdata(dev); + unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2); + unsigned long empty_pairs = 0; + unsigned long flags; + u32 active, per, unclaimed = 0; + + spin_lock_irqsave(&sun8i_chip->lock, flags); + per = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + for (unsigned int pair = 0; pair < num_pairs; pair++) { + u32 pair_mask = sun8i_pwm_pair_enable_mask(sun8i_chip, pair); + u32 owner_mask; + u32 pdzcr; + + owner_mask = sun8i_pwm_pair_owner_mask_locked(sun8i_chip, pair); + pdzcr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PDZCR(pair)); + /* Dead-zone mode couples both outputs into one waveform. */ + if ((pdzcr & SUN8I_PWM_PDZCR_ENABLE) && + (per & owner_mask)) + continue; + + unclaimed |= pair_mask & ~owner_mask; + } + + active = per & unclaimed; + per &= ~unclaimed; + sun8i_pwm_writel(sun8i_chip, per, SUN8I_PWM_PER); + + for (unsigned int pair = 0; pair < num_pairs; pair++) { + unsigned int first = pair * 2; + u32 pccr; + + pccr = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + if (unclaimed & SUN8I_PWM_ENABLE(first)) + pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first)); + if (first + 1 < sun8i_chip->data->npwm && + unclaimed & SUN8I_PWM_ENABLE(first + 1)) + pccr &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(first + 1)); + sun8i_pwm_writel(sun8i_chip, pccr, SUN8I_PWM_PCCR(pair)); + + if (!(per & sun8i_pwm_pair_enable_mask(sun8i_chip, pair))) + empty_pairs |= BIT(pair); + } + spin_unlock_irqrestore(&sun8i_chip->lock, flags); + + /* + * Reconcile empty pair gates through CCF. Taking and dropping a + * temporary reference leaves a gate with another CCF user enabled, but + * disables a firmware-enabled gate whose software count is still zero. + */ + for (unsigned int pair = 0; pair < num_pairs; pair++) { + struct clk *pair_clk; + int ret; + + if (!(empty_pairs & BIT(pair))) + continue; + + pair_clk = sun8i_chip->channels[pair * 2].pair_clk; + ret = clk_prepare_enable(pair_clk); + if (ret) { + dev_warn(dev, "Failed to synchronize pair %u gate: %pe\n", + pair, ERR_PTR(ret)); + continue; + } + clk_disable_unprepare(pair_clk); + } + + if (active) + dev_dbg(dev, "disabled unclaimed firmware outputs %#x\n", active); +} + +static int sun8i_pwm_init_clocks(struct device *dev, + struct sun8i_pwm_chip *sun8i_chip) +{ + unsigned int num_pairs = DIV_ROUND_UP(sun8i_chip->data->npwm, 2); + size_t hw_data_size; + int ret; + + sun8i_chip->pairs = devm_kcalloc(dev, num_pairs, + sizeof(*sun8i_chip->pairs), GFP_KERNEL); + if (!sun8i_chip->pairs) + return -ENOMEM; + + hw_data_size = struct_size(sun8i_chip->hw_data, hws, + sun8i_chip->data->npwm); + sun8i_chip->hw_data = devm_kzalloc(dev, hw_data_size, GFP_KERNEL); + if (!sun8i_chip->hw_data) + return -ENOMEM; + sun8i_chip->hw_data->num = sun8i_chip->data->npwm; + sun8i_pwm_sanitize_disabled_pairs(sun8i_chip); + + ret = sun8i_pwm_register_pair_clocks(dev, sun8i_chip); + if (ret) + return ret; + + return sun8i_pwm_register_channel_clocks(dev, sun8i_chip); +} + +static int sun8i_pwm_probe(struct platform_device *pdev) +{ + const struct sun8i_pwm_data *data; + struct device *dev = &pdev->dev; + struct sun8i_pwm_chip *sun8i_chip; + struct reset_control *rst; + struct pwm_chip *chip; + int ret; + + data = of_device_get_match_data(dev); + if (!data) + return dev_err_probe(dev, -ENODEV, + "Missing specific data structure\n"); + + chip = devm_pwmchip_alloc(dev, data->npwm, sizeof(*sun8i_chip)); + if (IS_ERR(chip)) + return dev_err_probe(dev, PTR_ERR(chip), + "Failed to allocate pwmchip\n"); + + sun8i_chip = sun8i_pwm_from_chip(chip); + sun8i_chip->data = data; + spin_lock_init(&sun8i_chip->lock); + sun8i_chip->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sun8i_chip->base)) + return dev_err_probe(dev, PTR_ERR(sun8i_chip->base), + "Failed to get PWM base address\n"); + + sun8i_chip->bus_clk = devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(sun8i_chip->bus_clk)) + return dev_err_probe(dev, PTR_ERR(sun8i_chip->bus_clk), + "Failed to get bus clock\n"); + + rst = devm_reset_control_get_shared_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "Failed to get reset control\n"); + + sun8i_chip->channels = devm_kcalloc(dev, data->npwm, + sizeof(*sun8i_chip->channels), + GFP_KERNEL); + if (!sun8i_chip->channels) + return dev_err_probe(dev, -ENOMEM, + "Failed to allocate %d channels array\n", + data->npwm); + + chip->ops = &sun8i_pwm_ops; + + ret = sun8i_pwm_init_clocks(dev, sun8i_chip); + if (ret) + return ret; + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, + sun8i_chip->hw_data); + if (ret) + return dev_err_probe(dev, ret, "Failed to add HW clock provider\n"); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + platform_set_drvdata(pdev, sun8i_chip); + + return 0; +} + +static const struct sun8i_pwm_data sun50i_h616_pwm_data = { + .npwm = 6, +}; + +static const struct of_device_id sun8i_pwm_dt_ids[] = { + { + .compatible = "allwinner,sun50i-h616-pwm", + .data = &sun50i_h616_pwm_data, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids); + +static struct platform_driver sun8i_pwm_driver = { + .driver = { + .name = "sun8i-pwm", + .of_match_table = sun8i_pwm_dt_ids, + .sync_state = sun8i_pwm_sync_state, + }, + .probe = sun8i_pwm_probe, +}; +module_platform_driver(sun8i_pwm_driver); + +MODULE_AUTHOR("Richard Genoud "); +MODULE_DESCRIPTION("Allwinner sun8i PWM driver"); +MODULE_LICENSE("GPL"); From patchwork Tue Aug 4 21:27:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 2941 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10]) by mxe881.netcup.net (Postfix) with ESMTPS id 8099D1C0254 for ; Tue, 4 Aug 2026 23:29:44 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-25046-noreply=patchwork.local@lists.linux.dev smtp.helo=sea.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.234.253.10 as permitted sender) client-ip=172.234.253.10; envelope-from=linux-sunxi+bounces-25046-noreply=patchwork.local@lists.linux.dev; helo=sea.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id DDBEC3046533 for ; Tue, 4 Aug 2026 21:27:45 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 7073947127A; Tue, 4 Aug 2026 21:27:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="nDPM2Uaw" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-oi1-f178.google.com (mail-oi1-f178.google.com [209.85.167.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 224AF41E6DA for ; Tue, 4 Aug 2026 21:27:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878865; cv=none; b=pqjxr7t24Mf/7+Lpwxtf9Y/PecrZKmY8iDQJdZzV12ru8zJMffyPMVtCKzVUPbzICg/Xu9Bx5mcd7cTVZrpsc6HeKA0snDVVBRgadTtAlLtNKv8SWQs8pc+GyvbAi6Fa7H9CQeT5aT2yY9GI5J5ZnERfG4Am1RI3ilvk514Hebo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878865; c=relaxed/simple; bh=cxjDKZ0MA9VUms6JcpknLJlKdT7ZoJ5aaylgo7tCtYM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=g09T8v5JFcjkIAKBmTIETpz3ZZogGVopN82I54kggeaCEoUxY8tKKivoFLbk+lyxQPthFll75HVhb29xBC5sBMhMGOPJQLIgjT200lYu4uGOduxjbuqC19W3meBt1ATKtOEqipR9B/WWHg6XJYuQIK5R4HTCH9Yh3fP6YFiqbfY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=nDPM2Uaw; arc=none smtp.client-ip=209.85.167.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-oi1-f178.google.com with SMTP id 5614622812f47-4a4c6081f9fso121281b6e.3 for ; Tue, 04 Aug 2026 14:27:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785878861; x=1786483661; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=FHj476tA2byKc8arhZaw8Ci81fHtdMiGqz0hVZIDFng=; b=nDPM2UawZxVD8Dxl+Vr834TnUi94C90AL+Rw0eByvudtd1jsLyvjPUOBed3ZQxf9Dl gGxQEHAlO0fAfPYketsc5ndi5CIRVbIty7+JSHLqMFtQ6kBPRjDLTwBIzEYfBQs5lqr2 SJUmEG90OYkNE9vO7kz3HlhSUmqmpD5JY9prUlq0061q6hZ4ECzro5xpLZnmtr4M09ht XbmljQuhIig/O3gsw2eU2gkeFIMYZm4OI0tWYTw8kcsySDxYusMW5nCxrmr8CyVHNXZO ZI7FKrKLQ71It0Dh8n9kSDFcyEnlhqv82z+OAm3DeTuNpud1L769uH0fU9RmOFE9S/S0 lb1w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785878861; x=1786483661; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=FHj476tA2byKc8arhZaw8Ci81fHtdMiGqz0hVZIDFng=; b=UYnM28dP8oFxvg92nUTcabgMs7U+N3wKnMY1Cr6alVvg3pIiXhiKsIfQDgS6ahmotz NGYZj4wCig7Op5+G39qrCk9Q67NJG8jP+Oqxj1F2QtvdG1SCF8NrwdF4Nf98tQRloEVi Xq8HJCC7qha89pVR6cvLfd4PV7J1HzX0yJi0ow5UnFkrpdErM74Rb8Abo1xc9IY6rvSN OgVhC0MNXJz41yVK28VRRQpYokwuAjqZaKU/xx9+D5jbJQCB8ZsUy82099UeICbWDuhF rHVOi1mKEb7OGGjqWNyAGthWhZV7JI84uyyPgwEkpVHMnEVFxhPV8EbF0+yoeNbXLNzY 3qmw== X-Forwarded-Encrypted: i=1; AHgh+RozHEE3iwV/K/dpYB3KsvjnInEc45tHwHCZmfLScbY+FfKhd9wmI2OomBX7HjgUMGUAm90B6XSOg0ii8w==@lists.linux.dev X-Gm-Message-State: AOJu0YyNmqpf1GV3ITGeKzDry0Hdb6xSsYBcmFR42N9ydjDYhHCox48E wghmOPnhtW31uWmvlOYgdu9wwB5laOrLGGAOMScXtE1Nn9h1viWZAYKF X-Gm-Gg: AR+sD105kae2a6yEX1sD6yyL0U6BURb0V5BKsgBGm0b5GgTl+z63YV6idCPRjdfsFrQ ZEAQoVJ3U0OUiGuT14o1WXdVI8bOv6qfA3jxxymxwQ6hyY8yANNU8QLNpYeeyJHfof/3bcoKbZk 1XWI2ASCEW6EnUwI+Mv209zcik6yf94Vj6w6ZjIrBUAo5wm5XuoDRs+8ELXp3Ys4LVYtlW0+dOW EAI5To5N/UY0ptc0SA+7CIamtH7QvpROMSNDzW9uKN4HgiqlJKaMg9xWDc8GMCU3eEGiGRrM/u0 GL9XAHxJFLyg4xUPTQKMV9UuU3GAGbg4IUyabCsE//Qzb3kT74+DwfmaDFtrpTjJgcXKmieBjXE 3eNvEKbiVVmBNEm8YGuTMVGNfPZ3Xd+c+VN1w4SZCYz74tvI/SO6iabc6pSQ7j0JMYfZncpCBD5 XKm53Tbnd0ahV3nagwcJPMR8Yz8GOaWeiFHSi1VEOYwri4j1NzLQxlE4G4JAliiugsCOZWh3gdo Fsbr7Nw9PyoW8NgrRpQJEWlkshoLZoWVwJYzlNbVt8fY7VASQ3KeVHndPB/JrYk9aqCoahmQbcr mR1fh4OEtIU9HRz97L+LEzPHztQ+oToUAnXEK+IVofeeT06Rnw3WNNblWKaCQHGfLk5JpPQTZPF GGSkWCrNQ4bn57VlL X-Received: by 2002:a05:6808:1646:b0:496:559d:5474 with SMTP id 5614622812f47-4afadfb0140mr961793b6e.8.1785878860975; Tue, 04 Aug 2026 14:27:40 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 5614622812f47-4afae7c50e9sm451659b6e.17.2026.08.04.14.27.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 14:27:40 -0700 (PDT) From: James Hilliard Date: Tue, 04 Aug 2026 15:27:26 -0600 Subject: [PATCH v8 3/4] arm64: dts: allwinner: h616: add PWM controller Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260804-h616-pwm-v8-v8-3-db37ab8624ae@gmail.com> References: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> In-Reply-To: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Maxime Ripard , Richard Genoud , Philipp Zabel , Michael Turquette , Stephen Boyd , Brian Masney Cc: linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, Paul Kocialkowski , Thomas Petazzoni , John Stultz , Joao Schim , bigunclemax@gmail.com, linux-clk@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [-1.16 / 15.00]; BAYES_HAM(-5.50)[100.00%]; RBL_SENDERSCORE(2.00)[172.234.253.10:from]; SUSPICIOUS_RECIPS(1.50)[]; DMARC_POLICY_SOFTFAIL(1.00)[gmail.com : SPF not aligned (relaxed), No valid DKIM,none]; MAILLIST(-0.15)[generic]; MIME_GOOD(-0.10)[text/plain]; BAD_REP_POLICIES(0.10)[]; HAS_LIST_UNSUB(-0.01)[]; PRECEDENCE_BULK(0.00)[]; FROM_HAS_DN(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[sea.lore.kernel.org:rdns,sea.lore.kernel.org:helo,bootlin.com:email]; TAGGED_RCPT(0.00)[dt]; FORGED_SENDER_MAILLIST(0.00)[]; FREEMAIL_CC(0.00)[vger.kernel.org,lists.infradead.org,lists.linux.dev,sys-base.io,bootlin.com,google.com,schimsalabim.eu,gmail.com]; RCPT_COUNT_TWELVE(0.00)[25]; RCVD_COUNT_FIVE(0.00)[6]; ASN(0.00)[asn:63949, ipnet:172.234.224.0/19, country:SG]; R_SPF_ALLOW(0.00)[+ip4:172.234.253.10]; FREEMAIL_TO(0.00)[kernel.org,gmail.com,sholland.org,bootlin.com,pengutronix.de,baylibre.com,redhat.com]; FREEMAIL_FROM(0.00)[gmail.com]; TO_DN_SOME(0.00)[]; FROM_NEQ_ENVFROM(0.00)[jameshilliard1@gmail.com,linux-sunxi@lists.linux.dev]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; RCVD_TLS_LAST(0.00)[]; TAGGED_FROM(0.00)[bounces-25046-noreply=patchwork.local]; MIME_TRACE(0.00)[0:+]; MID_RHS_MATCH_FROM(0.00)[]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; RCVD_VIA_SMTP_AUTH(0.00)[] X-Rspamd-Queue-Id: 8099D1C0254 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= From: Richard Genoud The H616 has a PWM controller that can provide PWM signals, but also plain clocks. Add the PWM controller node and pins in the device tree. Signed-off-by: Richard Genoud Signed-off-by: James Hilliard --- arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi index 1598e86259ab..90fe56a1d3cd 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi @@ -281,6 +281,17 @@ watchdog: watchdog@30090a0 { clocks = <&osc24M>; }; + pwm: pwm@300a000 { + compatible = "allwinner,sun50i-h616-pwm"; + reg = <0x0300a000 0x400>; + clocks = <&osc24M>, <&ccu CLK_BUS_PWM>; + clock-names = "mod", "bus"; + resets = <&ccu RST_BUS_PWM>; + #pwm-cells = <3>; + #clock-cells = <1>; + status = "disabled"; + }; + pio: pinctrl@300b000 { compatible = "allwinner,sun50i-h616-pinctrl"; reg = <0x0300b000 0x400>; @@ -385,6 +396,42 @@ nand_rb1_pin: nand-rb1-pin { bias-pull-up; }; + /omit-if-no-ref/ + pwm0_pin: pwm0-pin { + pins = "PD28"; + function = "pwm0"; + }; + + /omit-if-no-ref/ + pwm1_pin: pwm1-pin { + pins = "PG19"; + function = "pwm1"; + }; + + /omit-if-no-ref/ + pwm2_pin: pwm2-pin { + pins = "PH2"; + function = "pwm2"; + }; + + /omit-if-no-ref/ + pwm3_pin: pwm3-pin { + pins = "PH0"; + function = "pwm3"; + }; + + /omit-if-no-ref/ + pwm4_pin: pwm4-pin { + pins = "PI14"; + function = "pwm4"; + }; + + /omit-if-no-ref/ + pwm5_pin: pwm5-pin { + pins = "PA12"; + function = "pwm5"; + }; + /omit-if-no-ref/ spi0_pins: spi0-pins { pins = "PC0", "PC2", "PC4"; From patchwork Tue Aug 4 21:27:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 2942 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10]) by mxe881.netcup.net (Postfix) with ESMTPS id F20581C0254 for ; Tue, 4 Aug 2026 23:30:12 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-25048-noreply=patchwork.local@lists.linux.dev smtp.helo=sea.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.234.253.10 as permitted sender) client-ip=172.234.253.10; envelope-from=linux-sunxi+bounces-25048-noreply=patchwork.local@lists.linux.dev; helo=sea.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id E4723304D7CA for ; Tue, 4 Aug 2026 21:27:48 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 0BE0246D2BE; Tue, 4 Aug 2026 21:27:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="L+YknqrO" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-oi1-f171.google.com (mail-oi1-f171.google.com [209.85.167.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B4CF9472F8E for ; Tue, 4 Aug 2026 21:27:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878866; cv=none; b=hNgyvYawcGG987dSdqf46+OvD/rvpg9UrMnzgRI6MKcEh8tcLPNqgEzttA17IwZFdn5QY61/MbpH+L+1nMHR8a0nzMhjYtB8TCIxMrG1QEzmyGh4IbdinQU5wajex+fzPY5rDfSF7EXlEFTSpSovWXSKGKC6XX5m8ssr1JS395c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785878866; c=relaxed/simple; bh=VGb1jfHkMVxOapYytE16mS2xansj6G9Y/AJehlGO+Js=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uG4yS5KuM6LRlKMr4p/h9L3z83em/2XKYSQ+10InF4ME5sLzlSsQRijA0PxF/+h8q9nyvoytkoDX7uZ8cOSFcty9LxnsbP+wt2xIGZBeiQHBCcat1445we77hGM6eI7zPyyd1455FGwQprVB0y1XXc5Ig+eIZ1T75dcxO9QJREU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=L+YknqrO; arc=none smtp.client-ip=209.85.167.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-oi1-f171.google.com with SMTP id 5614622812f47-497e6956acfso262418b6e.1 for ; Tue, 04 Aug 2026 14:27:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785878862; x=1786483662; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Elx81wKiFvHIBGMAjiuzWwDTmSw0D6ljnUBG1nDCbuk=; b=L+YknqrOOgC02s1QNTu6Be2OgPNsy+nTIMELh7kLpz1owMB3OSx4MebxKbfPqztKM8 1ApfVDmorJ8p/ICRszstNUe5hPWHYeq9fT1yVzVEiIbbyjAQmhW4PKFOq5XQGG4yqjXJ Hh1oO6I7N5Gj9Ii1YFiLgAlnMM5X6me6xk2xlbIXySZKbz/vbzAb1Yucru96K1yk+Sod Q79I4mpXF0DfAEpQWjjBlbzbFzsc63U0AFy6KcHtlykSjV3JYf+Hwq5HXTBYYCkB+20L 01bnUE5J2N0CwggJTZYCaQZvrCVqpCM0q7qqsUZ+WKF2RAbKNWLmlr3UP4uqzxMS/aHz wYkg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785878862; x=1786483662; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=Elx81wKiFvHIBGMAjiuzWwDTmSw0D6ljnUBG1nDCbuk=; b=rgLqU3TFR2xD1BatnFQqmXhvbGvEkzVGhPqpn2+mI8ZR7R7AQerRwJvaTj38gnCPn+ V+h9r/w4v2p3SSmrZTz3W0AjsnoMRE7i9G0XkSXBzq0RPGVwzPuvhiWEZm8IoTU6dkFb jI9dd6rwUNggFK4QWSzOrLpqeJMnkWicr1o3pfqsxR3tRsHn0s7un4NNEmdF3nWt73/8 mT8jYwYpOiFmIdpgeYhpW9V7wsy3Z3IG3O4+OyTynpUdDIarSD53IOlsg/bzN64HRFTk Kbakh54bvKUbcPOidniztfYwoht64IXEDYk/geUdQC26bUg+p6SA3A1kLifegu6czhvZ CNDA== X-Forwarded-Encrypted: i=1; AHgh+RpUi7BzQ6JPXcQyefV3A8V54wSZljPC4ndNaEHc/EU1Ym440imdc4oe6nE1CrXpCYf8n6qYLxkGlsQETA==@lists.linux.dev X-Gm-Message-State: AOJu0YzRtLV05jqJAkFC4HZOlqs6EmmAXZOlZSIx++9hLQBkbGRDdcjc qJ8eNn+Zzt5k9HlGaM/HN6mdXYJOydkt2aI4y9X0tYzPy0jpN93Wp5oY X-Gm-Gg: AR+sD10aMWhjUZPq+kXmx3bq5S7r6Ym9nfeuLiw5uoLiDnsKFmo6sKCTiAFmIDpUYWg 9MMZcfZ+bnFRu74VaxJrpIsYCWz/7EKEPx4oPekXJQOaxS2qFc+3/Vr/NGmFzhsWJStsiDM/oWD U+VKMkCZa4jJqypICeF8sAeLuPiHHrC0zlTxSXSolWKZXzxwC0OHxI0S0ll1Q0X0uB45diuCCSf NcAt0JIpvLZejHIyZK13ZmEuuSS+TXlC+lZomdlpdpg8c84muXA5bF+cE7s6ftdhLzpY5s+X/5P UiWknZHInHgUZMIBIrZJhugOaJ4TKHuxCNNngl1YbZ5wNZzw4mGsYcrS3yxgx19GTEDHAS8XhNm kiKI+fvWkRTbiLcIeOSuzDKnThr3o6txgl/isW7tQINEj0YJBtrMOnPMUfhycd3yhL7g8NT0d2a LrIOxHXE5EhxcDblZRyJpDd4Hw/jZy0XilHeCzQYFOL9kobfXu/1IF/PuQXarEa681YjC0Mlxnq e3sGI+YogsKqA1kpXomF+90v+OReE8tYfqEarTh0uKbvVeIF5ZV+XDndQYDmLSEVEUY0AWavH9M sf4ModcvbEFGRvLdA+7qxBGNuDmSB+uc49lPCKWX1cl+ChpJEH5YgRrALetWgei2vVknL6Je+Ro oSneB0QIETiFxHr6CPY0hi7r6Ufo= X-Received: by 2002:a05:6808:3510:b0:4a4:989:bb1f with SMTP id 5614622812f47-4af98935a91mr6058342b6e.9.1785878862648; Tue, 04 Aug 2026 14:27:42 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 5614622812f47-4afae7c50e9sm451659b6e.17.2026.08.04.14.27.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 14:27:41 -0700 (PDT) From: James Hilliard Date: Tue, 04 Aug 2026 15:27:27 -0600 Subject: [PATCH v8 4/4] MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260804-h616-pwm-v8-v8-4-db37ab8624ae@gmail.com> References: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> In-Reply-To: <20260804-h616-pwm-v8-v8-0-db37ab8624ae@gmail.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen-Yu Tsai , Jernej Skrabec , Samuel Holland , Maxime Ripard , Richard Genoud , Philipp Zabel , Michael Turquette , Stephen Boyd , Brian Masney Cc: linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-sunxi@lists.linux.dev, linux-kernel@vger.kernel.org, Paul Kocialkowski , Thomas Petazzoni , John Stultz , Joao Schim , bigunclemax@gmail.com, linux-clk@vger.kernel.org, James Hilliard X-Mailer: b4 0.15.2 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [-1.16 / 15.00]; BAYES_HAM(-5.50)[99.99%]; RBL_SENDERSCORE(2.00)[172.234.253.10:from]; SUSPICIOUS_RECIPS(1.50)[]; DMARC_POLICY_SOFTFAIL(1.00)[gmail.com : SPF not aligned (relaxed), No valid DKIM,none]; MAILLIST(-0.15)[generic]; BAD_REP_POLICIES(0.10)[]; MIME_GOOD(-0.10)[text/plain]; HAS_LIST_UNSUB(-0.01)[]; TAGGED_RCPT(0.00)[dt]; PRECEDENCE_BULK(0.00)[]; RCPT_COUNT_TWELVE(0.00)[25]; TO_DN_SOME(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[sea.lore.kernel.org:rdns,sea.lore.kernel.org:helo,bootlin.com:email]; FORGED_SENDER_MAILLIST(0.00)[]; FREEMAIL_CC(0.00)[vger.kernel.org,lists.infradead.org,lists.linux.dev,sys-base.io,bootlin.com,google.com,schimsalabim.eu,gmail.com]; FREEMAIL_FROM(0.00)[gmail.com]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; R_SPF_ALLOW(0.00)[+ip4:172.234.253.10:c]; MIME_TRACE(0.00)[0:+]; RCVD_TLS_LAST(0.00)[]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; FREEMAIL_TO(0.00)[kernel.org,gmail.com,sholland.org,bootlin.com,pengutronix.de,baylibre.com,redhat.com]; FROM_HAS_DN(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; ASN(0.00)[asn:63949, ipnet:172.234.224.0/19, country:SG]; TAGGED_FROM(0.00)[bounces-25048-noreply=patchwork.local]; RCVD_COUNT_FIVE(0.00)[6]; FROM_NEQ_ENVFROM(0.00)[jameshilliard1@gmail.com,linux-sunxi@lists.linux.dev]; RCVD_VIA_SMTP_AUTH(0.00)[] X-Rspamd-Queue-Id: F20581C0254 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= From: Richard Genoud Add myself as maintainer of Allwinner SUN8I PWM driver. Signed-off-by: Richard Genoud Signed-off-by: James Hilliard --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5114e6db7307..9a778d1242c5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -919,6 +919,11 @@ S: Maintained F: Documentation/devicetree/bindings/hwlock/allwinner,sun6i-a31-hwspinlock.yaml F: drivers/hwspinlock/sun6i_hwspinlock.c +ALLWINNER SUN8I PWM DRIVER +M: Richard Genoud +S: Maintained +F: drivers/pwm/pwm-sun8i.c + ALLWINNER THERMAL DRIVER M: Vasily Khoruzhick M: Yangtao Li