| Message ID | 20260416134037.3160537-1-richard.genoud@bootlin.com (mailing list archive) |
|---|---|
| Headers |
Return-Path: <linux-sunxi+bounces-22857-sunxi=pue.re@lists.linux.dev> 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 A5B951C0747 for <noreply@patchwork.local>; Thu, 16 Apr 2026 15:45:52 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=bootlin.com; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-22857-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-22857-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 634F3301C145 for <noreply@patchwork.local>; Thu, 16 Apr 2026 13:40:59 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 24C7039D6C7; Thu, 16 Apr 2026 13:40:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="M85td3UB" X-Original-To: linux-sunxi@lists.linux.dev Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (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 311AE3BF680 for <linux-sunxi@lists.linux.dev>; Thu, 16 Apr 2026 13:40:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776346859; cv=none; b=LToJPyY4esK+AD0nivRcNwdhOP3bNf5TB6ECQzhwgUpLjoZPXAIWzcbKQ+uYDD6vLFkwo6WLRuBMxks5wN1cXHpdQFvHBQy3KJJmiOX8ju7IAyvBylmCh4C1Olh5/9GO+Qso0PVePOkrnz+I75bj0Q1Vn7QZ/qHBb97rdot1IUo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776346859; c=relaxed/simple; bh=KiLV3fc/9tqWwXn5cO7w/9PDOIj4VRuvWmojL/UyqFg=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=gU7q/VoNw9J7Dvm+As0ZCbZHSd0fjPGqVVTpEXqtBhPMsAE5MwwWfh4XWkV3EpeUq8Z4BaGPiXx3CNPdKz7g31x/SgC0OSDYeO91Oz81NJFACxEPSlppsK49NBhSoZ4UzPLocx7/jBvdawtNjKy78CMJ9mugIBSsZibvbIZOiu8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=M85td3UB; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id BCB224E42A1E; Thu, 16 Apr 2026 13:40:55 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 9201B60495; Thu, 16 Apr 2026 13:40:55 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id BCAE3104591FB; Thu, 16 Apr 2026 15:40:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1776346854; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding; bh=JmeDH1C+TChiUEEAvCZY7BpLUkWXbXyiCVPuzrN7nzw=; b=M85td3UB9vvYAePJgAnZ5ZFLUg79jXMMfletwmbRmgvEMoUihQVvC9PrTs9G79QIkClkzt b5IVstW7A90vWQEogjezjhhpM3LQ6GI8pyT54Sda0d9F0koUB9wr7syjrhrxZNDh/HzdTw /eI5/tjZ8oJ9+cjbvdLxSbLtPE6Yb1Q5jrMcfg9D2zFoCm1Qbru5vfvpusCuJl7xqc7AtW v7J9lrRbDh0krg+AoibOLqiC/MkOIVW9HFbNKeMGaRb48Ym+dWYeuTyvgGDchNKbHwY1j9 LM76lkBWk14rLnKHo2Fc4iJ7EeOqbLjngDPviJ/1U4iEI3AweBwELFpB5ESxzA== From: Richard Genoud <richard.genoud@bootlin.com> To: =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= <ukleinek@kernel.org>, Rob Herring <robh@kernel.org>, Krzysztof Kozlowski <krzk+dt@kernel.org>, Conor Dooley <conor+dt@kernel.org>, Chen-Yu Tsai <wens@kernel.org>, Jernej Skrabec <jernej.skrabec@gmail.com>, Samuel Holland <samuel@sholland.org>, Philipp Zabel <p.zabel@pengutronix.de> Cc: Paul Kocialkowski <paulk@sys-base.io>, Thomas Petazzoni <thomas.petazzoni@bootlin.com>, John Stultz <jstultz@google.com>, Joao Schim <joao@schimsalabim.eu>, bigunclemax@gmail.com, 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, Richard Genoud <richard.genoud@bootlin.com> Subject: [PATCH v6 0/4] Introduce Allwinner H616 PWM controller Date: Thu, 16 Apr 2026 15:40:33 +0200 Message-ID: <20260416134037.3160537-1-richard.genoud@bootlin.com> X-Mailer: git-send-email 2.47.3 Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: <linux-sunxi.lists.linux.dev> List-Subscribe: <mailto:linux-sunxi+subscribe@lists.linux.dev> List-Unsubscribe: <mailto:linux-sunxi+unsubscribe@lists.linux.dev> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Last-TLS-Session-Version: TLSv1.3 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [-0.66 / 15.00]; BAYES_HAM(-5.50)[100.00%]; RBL_SENDERSCORE(2.00)[172.234.253.10:from]; SUSPICIOUS_RECIPS(1.50)[]; MID_CONTAINS_FROM(1.00)[]; R_MISSING_CHARSET(0.50)[]; MAILLIST(-0.15)[generic]; MIME_GOOD(-0.10)[text/plain]; BAD_REP_POLICIES(0.10)[]; HAS_LIST_UNSUB(-0.01)[]; TAGGED_RCPT(0.00)[dt]; FROM_HAS_DN(0.00)[]; PRECEDENCE_BULK(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[sea.lore.kernel.org:rdns,sea.lore.kernel.org:helo,bootlin.com:dkim]; RCPT_COUNT_TWELVE(0.00)[19]; FUZZY_BLOCKED(0.00)[rspamd.com]; FREEMAIL_CC(0.00)[sys-base.io,bootlin.com,google.com,schimsalabim.eu,gmail.com,vger.kernel.org,lists.infradead.org,lists.linux.dev]; RCVD_COUNT_SEVEN(0.00)[7]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_NEQ_ENVFROM(0.00)[richard.genoud@bootlin.com,linux-sunxi@lists.linux.dev]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; R_SPF_ALLOW(0.00)[+ip4:172.234.253.10:c]; DKIM_TRACE(0.00)[bootlin.com:+]; TO_DN_SOME(0.00)[]; FORGED_SENDER_MAILLIST(0.00)[]; RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[100.90.174.1:received,185.246.85.4:received,212.83.139.233:received,212.83.136.155:received]; R_DKIM_ALLOW(0.00)[bootlin.com:s=dkim]; DMARC_POLICY_ALLOW(0.00)[bootlin.com,reject]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; TAGGED_FROM(0.00)[bounces-22857-noreply=patchwork.local]; FREEMAIL_TO(0.00)[kernel.org,gmail.com,sholland.org,pengutronix.de]; ASN(0.00)[asn:63949, ipnet:172.234.224.0/19, country:SG]; MIME_TRACE(0.00)[0:+]; RCVD_TLS_LAST(0.00)[]; RBL_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[172.234.253.10:from] X-Rspamd-Queue-Id: A5B951C0747 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?= |
| Series | Introduce Allwinner H616 PWM controller | |
Message
Richard Genoud
April 16, 2026, 1:40 p.m. UTC
Allwinner H616 PWM controller is quite different from the A10 one.
It can drive 6 PWM channels, and like for the A10, each channel has a
bypass that permits to output a clock, bypassing the PWM logic, when
enabled.
But, the channels are paired 2 by 2, sharing a first set of
MUX/prescaler/gate.
Then, for each channel, there's another prescaler (that will be bypassed
if the bypass is enabled for this channel).
It looks like that:
_____ ______ ________
OSC24M --->| | | | | |
APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy
|_____| |______| |________|
________
| |
+->| /div_k |---> PWM_clock_x
| |________|
| ______
| | |
+-->| Gate |----> PWM_bypass_clock_x
| |______|
PWM_clock_src_xy -----+ ________
| | |
+->| /div_k |---> PWM_clock_y
| |________|
| ______
| | |
+-->| Gate |----> PWM_bypass_clock_y
|______|
Where xy can be 0/1, 2/3, 4/5
PWM_clock_x/y serve for the PWM purpose.
PWM_bypass_clock_x/y serve for the clock-provider purpose.
The common clock framework has been used to manage those clocks.
This PWM driver serves as a clock-provider for PWM_bypass_clocks.
This is needed for example by the embedded AC300 PHY which clock comes
from PMW5 pin (PB12).
Usually, to get a clock from a PWM driver, we use the pwm-clock driver
so that the PWM driver doesn't need to be a clk-provider itself.
While this works in most cases, here it just doesn't.
That's because the pwm-clock request a period from the PWM driver,
without any clue that it actually wants a clock at a specific frequency,
and not a PWM signal with duty cycle capability.
So, the PWM driver doesn't know if it can use the bypass or not, it
doesn't even have the real accurate frequency information (23809524 Hz
instead of 24MHz) because PWM drivers only deal with periods.
With pwm-clock, we loose a precious information along the way (that we
actually want a clock and not a PWM signal).
That's ok with simple PWM drivers that don't have multiple input clocks,
but in this case, without this information, we can't know for sure which
clock to use.
And here, for instance, if we ask for a 24MHz clock, pwm-clock will
requests 42ns (assigned-clocks doesn't help for that matter). The logic
is to select the highest clock (100MHz) with no prescaler and a duty
cycle value of 2/4 => we have 25MHz instead of 24MHz.
And that's a perfectly fine choice for a PMW, because we still can
change the duty cycle in the range [0-4]/4.
But obviously for a clock, we don't care about the duty cycle, but more
about the clock accuracy.
And actually, this PWM is really a PWM AND a real clock when the bypass
is set.
This series is based onto v7.0
NB: checkpatch is not happy with patch 2, but it's a false positive.
It doesn't detect that PWM_XY_SRC_MUX/GATE/DIV are structures, but as
it's more readable like that, I prefer keeping it that way.
Changes since v5:
- remove trailing junk after commit message in patch 4
- remove Tested-by when it doesn't make sense.
(sorry for the noise)
Changes since v4:
- Fix a bug on bypass for channels greater than 1
- add colons to clarify 2 debug messages
- switch from H616 to sun8i prefix (in code, filename, module name)
- fix consistency issues in macro parameters
- rename some macros with a confusing naming
- rebase on v7.0
Changes since v3:
- gather Acked-by/Tested-by
- fix cast from pointer to integer of different size (kernel test robot
with arc platform)
- add devm_action for clk_hw_unregister_composite as suggested by Philipp
- remove now unused pwm_remove as suggested by Philipp
Changes since v2:
- use U32_MAX instead of defining UINT32_MAX
- add a comment on U32_MAX usage in clk_round_rate()
- change clk_table_div_m (use macros)
- fix formatting (double space, superfluous comma, extra line feed)
- fix the parent clock order
- simplify code by using scoped_guard()
- add missing const in to_h616_pwm_chip() and rename to
h616_pwm_from_chip()
- add/remove missing/superfluous error messages
- rename cnt->period_ticks, duty_cnt->duty_ticks
- fix PWM_PERIOD_MAX
- add .remove() callback
- fix DIV_ROUND_CLOSEST_ULL->DIV_ROUND_UP_ULL
- add H616_ prefix
- protect _reg in macros
- switch to waveforms instead of apply/get_state
- shrink struct h616_pwm_channel
- rebase on v6.19-rc4
Changes since v1:
- rebase onto v6.19-rc1
- add missing headers
- remove MODULE_ALIAS (suggested by Krzysztof)
- use sun4i-pwm binding instead of creating a new one (suggested by Krzysztof)
- retrieve the parent clocks from the devicetree
- switch num_parents to unsigned int
Richard Genoud (4):
dt-bindings: pwm: allwinner: add h616 pwm compatible
pwm: sun8i: Add H616 PWM support
arm64: dts: allwinner: h616: add PWM controller
MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver
.../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 19 +-
MAINTAINERS | 5 +
.../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 47 +
drivers/pwm/Kconfig | 12 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-sun8i.c | 938 ++++++++++++++++++
6 files changed, 1021 insertions(+), 1 deletion(-)
create mode 100644 drivers/pwm/pwm-sun8i.c
base-commit: 028ef9c96e96197026887c0f092424679298aae8
Comments
On Thu, 16 Apr 2026 15:40:33 +0200 Richard Genoud <richard.genoud@bootlin.com> wrote: Hi Richard, > Allwinner H616 PWM controller is quite different from the A10 one. > > It can drive 6 PWM channels, and like for the A10, each channel has a > bypass that permits to output a clock, bypassing the PWM logic, when > enabled. > > But, the channels are paired 2 by 2, sharing a first set of > MUX/prescaler/gate. > Then, for each channel, there's another prescaler (that will be bypassed > if the bypass is enabled for this channel). > > It looks like that: > _____ ______ ________ > OSC24M --->| | | | | | > APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy > |_____| |______| |________| > ________ > | | > +->| /div_k |---> PWM_clock_x > | |________| > | ______ > | | | > +-->| Gate |----> PWM_bypass_clock_x > | |______| > PWM_clock_src_xy -----+ ________ > | | | > +->| /div_k |---> PWM_clock_y > | |________| > | ______ > | | | > +-->| Gate |----> PWM_bypass_clock_y > |______| > > Where xy can be 0/1, 2/3, 4/5 > > PWM_clock_x/y serve for the PWM purpose. > PWM_bypass_clock_x/y serve for the clock-provider purpose. > The common clock framework has been used to manage those clocks. > > This PWM driver serves as a clock-provider for PWM_bypass_clocks. > This is needed for example by the embedded AC300 PHY which clock comes > from PMW5 pin (PB12). > > Usually, to get a clock from a PWM driver, we use the pwm-clock driver > so that the PWM driver doesn't need to be a clk-provider itself. > While this works in most cases, here it just doesn't. > That's because the pwm-clock request a period from the PWM driver, > without any clue that it actually wants a clock at a specific frequency, > and not a PWM signal with duty cycle capability. > So, the PWM driver doesn't know if it can use the bypass or not, it > doesn't even have the real accurate frequency information (23809524 Hz > instead of 24MHz) because PWM drivers only deal with periods. > > With pwm-clock, we loose a precious information along the way (that we > actually want a clock and not a PWM signal). > That's ok with simple PWM drivers that don't have multiple input clocks, > but in this case, without this information, we can't know for sure which > clock to use. > And here, for instance, if we ask for a 24MHz clock, pwm-clock will > requests 42ns (assigned-clocks doesn't help for that matter). The logic > is to select the highest clock (100MHz) with no prescaler and a duty > cycle value of 2/4 => we have 25MHz instead of 24MHz. Didn't we discuss this choice of "highest clock" before? I dimly remember asking whether we cannot use a least-error approach, so considering all clocks and choosing the one which matches the target best? > And that's a perfectly fine choice for a PMW, because we still can > change the duty cycle in the range [0-4]/4. > But obviously for a clock, we don't care about the duty cycle, but more > about the clock accuracy. Thanks for your research into this and summarising this up! I see your point, and always found the choice to use nanoseconds in PWM somewhat problematic, especially when looking at the rounding errors. And while turning the PWM into a clock provider looks like a clever solution, this is somewhat arbitrary - why do we have this for those SoCs and not the other (older) ones? The 24 MHz != 1/42ns problem is the same there. What also bugs me a bit is also that this is actually a decision affecting the generic hardware devicetree description of the system, but its rooted in a Linux implementation detail (ns resolution periods). So before we consider going this route: - Can we change the internal interface in Linux, maybe introducing some special interface from pwm-clock to the pwm drivers, to convey frequencies directly, instead of period lengths? - Can we add an optional interface for pwm drivers in general, to use a frequency / duty-cycle pair to describe a PWM setup? I would naively think those to be some kind of natural PWM parameters. - Can we at least add a picosecond precision interface? That doesn't solve the rounding issue for those number not only divisible by 2 or 5 (like 24), but at least it seems to mitigate it: 24 MHz => 41666 ps => 24.000384 MHz I see that having a clock provider seems like a more sustainable and fitting choice, but as mentioned it's something that affects the DT description, so I don't want to change that lightly. Cheers, Andre > And actually, this PWM is really a PWM AND a real clock when the bypass > is set. > > This series is based onto v7.0 > > NB: checkpatch is not happy with patch 2, but it's a false positive. > It doesn't detect that PWM_XY_SRC_MUX/GATE/DIV are structures, but as > it's more readable like that, I prefer keeping it that way. > > Changes since v5: > - remove trailing junk after commit message in patch 4 > - remove Tested-by when it doesn't make sense. > (sorry for the noise) > > Changes since v4: > - Fix a bug on bypass for channels greater than 1 > - add colons to clarify 2 debug messages > - switch from H616 to sun8i prefix (in code, filename, module name) > - fix consistency issues in macro parameters > - rename some macros with a confusing naming > - rebase on v7.0 > > Changes since v3: > - gather Acked-by/Tested-by > - fix cast from pointer to integer of different size (kernel test robot > with arc platform) > - add devm_action for clk_hw_unregister_composite as suggested by Philipp > - remove now unused pwm_remove as suggested by Philipp > > Changes since v2: > - use U32_MAX instead of defining UINT32_MAX > - add a comment on U32_MAX usage in clk_round_rate() > - change clk_table_div_m (use macros) > - fix formatting (double space, superfluous comma, extra line feed) > - fix the parent clock order > - simplify code by using scoped_guard() > - add missing const in to_h616_pwm_chip() and rename to > h616_pwm_from_chip() > - add/remove missing/superfluous error messages > - rename cnt->period_ticks, duty_cnt->duty_ticks > - fix PWM_PERIOD_MAX > - add .remove() callback > - fix DIV_ROUND_CLOSEST_ULL->DIV_ROUND_UP_ULL > - add H616_ prefix > - protect _reg in macros > - switch to waveforms instead of apply/get_state > - shrink struct h616_pwm_channel > - rebase on v6.19-rc4 > > Changes since v1: > - rebase onto v6.19-rc1 > - add missing headers > - remove MODULE_ALIAS (suggested by Krzysztof) > - use sun4i-pwm binding instead of creating a new one (suggested by Krzysztof) > - retrieve the parent clocks from the devicetree > - switch num_parents to unsigned int > > Richard Genoud (4): > dt-bindings: pwm: allwinner: add h616 pwm compatible > pwm: sun8i: Add H616 PWM support > arm64: dts: allwinner: h616: add PWM controller > MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver > > .../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 19 +- > MAINTAINERS | 5 + > .../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 47 + > drivers/pwm/Kconfig | 12 + > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-sun8i.c | 938 ++++++++++++++++++ > 6 files changed, 1021 insertions(+), 1 deletion(-) > create mode 100644 drivers/pwm/pwm-sun8i.c > > > base-commit: 028ef9c96e96197026887c0f092424679298aae8 >