From patchwork Tue Sep 22 00:06:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 3407 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 6379E1C36E5 for ; Tue, 22 Sep 2026 02:08:23 +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-26105-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-26105-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 2EC41628EE for ; Tue, 22 Sep 2026 00:06:53 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id DBF8F211466; Tue, 22 Sep 2026 00:06:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="j4Fgy7eA" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-oa2-f12.google.com (mail-oa2-f12.google.com [74.125.231.76]) (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 B13F91E1DF0 for ; Tue, 22 Sep 2026 00:06:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.231.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790035612; cv=none; b=VaDOPW4AjvNiFb8GIetYb5MCnZpr6AyQ2O9pZpGl2zX7W3MvYBkNcR/mj1kZLcsI0RJa96e46+q5XUwm/uJ2GUpCUXlm+Z1fic6noomfYBfeRXh8U4RzprCnoDg57V7rmLjWpZF4m0fxmKbJXn4ZcNxDrvTxr8QzDZYPc/xdYnI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790035612; c=relaxed/simple; bh=AwEdnSqwNldsU46K3L8s8kozlkZ4yxHPkhkqHOBcOAk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=donoPdW1W43XjxwOM1haMZ7QKAZfJ9uRengFwmnDWW271m4wHnELgAhFlgUHqml77EM5/HbWgOHLdTw894qhIOnXlbabHHJ/4bb2C9X5lBPBU6xaJol/ion1r3y2dsRJVBHvCtfSrNtEmgygXytyRbZ9nNIedVny86bSGl4nUls= 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=j4Fgy7eA; arc=none smtp.client-ip=74.125.231.76 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-oa2-f12.google.com with SMTP id 586e51a60fabf-466ccdd7695so1244114fac.1 for ; Mon, 21 Sep 2026 17:06:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790035607; x=1790640407; 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=1mzlKbz8Phj+ip4hm3yiFbRMiYi293gbL25g7vO3mK8=; b=j4Fgy7eAx7P7y8ZaZbNwUoO0cG9Je8JqE82B/yCzryxa1z2WKy4kHjNbIAk7YxvO22 N4iLw7+gWntYcU9mjO29MeIKOpLeZH7CGD5jzoOmFS6c66G3+E4mREvQ1cBNUyVhKgeO +HIZ7WMwbIUkYzH1SUtQUVt9xNM+nCWT+tFk4o5Li3+TI4dU5WVyyiZGePSnf06CX85Y 7uzDMdlxEzA6GxUS23507lt+la+pCqgjgWNu+fMFzhUQnGaXaYTFRMH2d196tSLn0Th1 nMnyhP2aDfpiLUB8Q4KG1ItO5tQ5ozFw6SgLm0HPKInVkWYCt5CFsKxhPIHbSRf7SacQ 2ZCw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790035607; x=1790640407; 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=1mzlKbz8Phj+ip4hm3yiFbRMiYi293gbL25g7vO3mK8=; b=Rg4P3wSVogQx+aLnfSsyM56jgMNClrjTSSqGo9Q8WX6UA1+6W6OxrxOBVKmQ1jq4gm UM81mUPf7g4pnknXCdh1iahUp0k3aXgXlxuejCNGXqn0kn8HzE8pbgFBIxvaL9wLB2wd 2q6cfuqw+R1kK+ioOJsZXF++YMLPudyywg4ByAGkFTOGqgRkFhpdM4gG1XD7vqsms5zE kcsgGEzgBfV+dIbGIhkXIBfIT/ak+2ikn/PtF/uWS4lFYqzgfjfA4B4eXVZnqLZCTPsw qqOAFhH7jWHaqoEIz4747jH32RmeJJN6YHwdhUVUmhTI8mriOp5rWVwsktFaPJWDnD/v tgDA== X-Forwarded-Encrypted: i=1; AKwUvBzci3uP8mk90ZOL0L9jCcHp9pqpahmBTZSZUmX7YIC0Gluszc44dQefrE4qW1L9sGCcqkxwrGOgEjNISw==@lists.linux.dev X-Gm-Message-State: AFuF++mrDH5IrKHrqMM/bpOJe1kobKTgR0coRkv1HzrugfXvB0XiZiaw a9ZZf4BNqdQxWyEXEPt1P0KykZd8xlw57PA9du1b0ohK8hUppt8znIZ2 X-Gm-Gg: AYBFou2QsXPeZop1HLARbVVUoa8kKcMkf1eaQKMiBXUR6+BiUE7TO3PUSbocsJ8aejT EHSjp56LJUI6Uvqi/CzWr39ZSIoacBOpFzkiJTF+QVzYJLzQSXA78TAZR+oBHz30x5WTZqo8Fim RNG2fLEq4DrVVg7oODHh1yUXA9weIgp2f2fbIvNhvVSbkcW/EOOuc+2yvYWW4Q32KiHdmdd8jZK rVxjaGM+vEN2uK8Q9xQz1WZR+8ItaWhpD/u2+GyA7F8GCgXPMN47nBAWQLnsYtJ0BfLXaYF3iqE PiWCV2qDe7r/B2suiybGY4GuOlQ4sLW0RgUzcNk+iAy4Xd0hBvghCc4SGjlR+UHeRD63AR4NypU EGUh3YrnH22ccqKHy1V39rncuiw6zRsKnosgIPwc/jRj6vdXVBt46sC7wxjS9/VBdQcJ5+A/oKb CmlyGD3ZGs9esNt4Bog2u6MMPKfx2SGdauvrNOrlo69YLB3sm/tUUD8hKwG8rCSCk8BVY+HnQdm PjsUR7+9eekTYAv6iNgB2bKAs45EoD83biBBLDlVi6e87xe0MxQ3M+9oNd1fBuc7Xczdoe8OFhR KvblcOsfktiHPX49fPwAbbWIvPakHT17Yf6/NTH7ON7AGgOHLJAx5iMer0FRX9zVkVRZXlRQ2YU H4Knuf4cGiY5IOQ58tmi1trNkr+/3+G0= X-Received: by 2002:a05:6870:14d4:b0:47f:b0ff:8383 with SMTP id 586e51a60fabf-486e4df7aa7mr12381394fac.1.1790035607564; Mon, 21 Sep 2026 17:06:47 -0700 (PDT) Received: from [127.0.1.1] (174-29-1-49.hlrn.qwest.net. [174.29.1.49]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-48f7280cf14sm801683fac.13.2026.09.21.17.06.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 21 Sep 2026 17:06:46 -0700 (PDT) From: James Hilliard Date: Mon, 21 Sep 2026 18:06:32 -0600 Subject: [PATCH v9 3/5] pwm: sun8i: export the bypass outputs as clocks Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260921-h616-pwm-v8-v9-3-37bec6201ff5@gmail.com> References: <20260921-h616-pwm-v8-v9-0-37bec6201ff5@gmail.com> In-Reply-To: <20260921-h616-pwm-v8-v9-0-37bec6201ff5@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]; BAD_REP_POLICIES(0.10)[]; MIME_GOOD(-0.10)[text/plain]; HAS_LIST_UNSUB(-0.01)[]; FORGED_SENDER_MAILLIST(0.00)[]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; FROM_HAS_DN(0.00)[]; RCPT_COUNT_TWELVE(0.00)[25]; PRECEDENCE_BULK(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]; TAGGED_RCPT(0.00)[dt]; DBL_BLOCKED_OPENRESOLVER(0.00)[bootlin.com:email,sea.lore.kernel.org:rdns,sea.lore.kernel.org:helo]; 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:c]; FREEMAIL_FROM(0.00)[gmail.com]; TO_DN_SOME(0.00)[]; RBL_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[172.234.253.10:from]; RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[174.29.1.49:received,74.125.231.76:received,100.90.174.1:received]; FROM_NEQ_ENVFROM(0.00)[jameshilliard1@gmail.com,linux-sunxi@lists.linux.dev]; FREEMAIL_TO(0.00)[kernel.org,gmail.com,sholland.org,bootlin.com,pengutronix.de,baylibre.com,redhat.com]; MIME_TRACE(0.00)[0:+]; TAGGED_FROM(0.00)[bounces-26105-noreply=patchwork.local]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; RCVD_TLS_LAST(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[] X-Rspamd-Queue-Id: 6379E1C36E5 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 Expose each H616 PWM channel's hardware bypass as a CCF clock. Consumers such as the co-packaged AC200/AC300 PHY need a clock rather than PWM duty or polarity control. Keep the pair source protected for the complete prepared lifetime, and arbitrate ownership between PWM requests and clock prepare so both interfaces cannot drive the same output. drivers/clk/clk-pwm.c advertises a fixed frequency without reading back the rounded PWM rate. A 42 ns, 21 ns request does select 24 MHz bypass when it is available. However, with the pair constrained to 100 MHz by its sibling, the PWM rounding rules select a 40 ns, 20 ns waveform (25 MHz), while a pwm-clock configured for 24 MHz still reports 24 MHz. The direct provider instead reports the actual pair rate and makes its shared-rate protection visible to CCF. Consumers must still check the rate they receive. Use CLK_IGNORE_UNUSED to preserve unclaimed firmware bypass outputs, just as the PWM path preserves inherited waveforms. Normal consumer disable still turns the output off and releases its shared gate reference. Signed-off-by: Richard Genoud Co-developed-by: James Hilliard Signed-off-by: James Hilliard --- drivers/pwm/Kconfig | 3 +- drivers/pwm/pwm-sun8i.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d87d166e7836..d3c35a11859f 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -754,7 +754,8 @@ config PWM_SUN8I depends on HAS_IOMEM && COMMON_CLK help PWM framework driver for Allwinner controllers whose channels share - paired source clocks. + 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. diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c index 7e6c39815c18..fd81c5f5e326 100644 --- a/drivers/pwm/pwm-sun8i.c +++ b/drivers/pwm/pwm-sun8i.c @@ -113,6 +113,9 @@ * 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. */ @@ -129,6 +132,12 @@ static const struct clk_div_table sun8i_pwm_div_m_table[] = { { /* sentinel */ } }; +enum sun8i_pwm_mode { + SUN8I_PWM_MODE_NONE, + SUN8I_PWM_MODE_PWM, + SUN8I_PWM_MODE_CLK, +}; + struct sun8i_pwm_chip; struct sun8i_pwm_pair { @@ -142,8 +151,12 @@ struct sun8i_pwm_pair { }; struct sun8i_pwm_channel { + struct clk_hw bypass_hw; + struct sun8i_pwm_chip *chip; + unsigned int index; /* Separate CCF consumer, held only from PWM request to free. */ struct clk *pair_clk; + enum sun8i_pwm_mode mode; u64 pending_period_ns; bool rate_exclusive; }; @@ -153,7 +166,7 @@ struct sun8i_pwm_chip { struct sun8i_pwm_channel channels[SUN8I_PWM_NPWM]; struct clk *bus_clk; void __iomem *base; - /* Protects shared registers and rate_exclusive. */ + /* Protects shared registers, channel ownership and rate_exclusive. */ spinlock_t lock; }; @@ -317,6 +330,101 @@ static int sun8i_pwm_pair_rate_notifier(struct notifier_block *nb, return active ? NOTIFY_BAD : NOTIFY_OK; } +static inline struct sun8i_pwm_channel * +sun8i_pwm_channel_from_hw(struct clk_hw *hw) +{ + return container_of(hw, struct sun8i_pwm_channel, bypass_hw); +} + +static int sun8i_pwm_bypass_prepare(struct clk_hw *hw) +{ + struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = chan->chip; + + guard(spinlock_irqsave)(&sun8i_chip->lock); + if (chan->mode != SUN8I_PWM_MODE_NONE) + return -EBUSY; + + chan->mode = SUN8I_PWM_MODE_CLK; + return 0; +} + +static void sun8i_pwm_bypass_unprepare(struct clk_hw *hw) +{ + struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = chan->chip; + + guard(spinlock_irqsave)(&sun8i_chip->lock); + if (chan->mode != SUN8I_PWM_MODE_CLK) + return; + + chan->mode = SUN8I_PWM_MODE_NONE; +} + +static int sun8i_pwm_bypass_enable(struct clk_hw *hw) +{ + struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = chan->chip; + unsigned int pair = SUN8I_PWM_PAIR_IDX(chan->index); + u32 pccr, per; + + guard(spinlock_irqsave)(&sun8i_chip->lock); + if (chan->mode != SUN8I_PWM_MODE_CLK) + return -EBUSY; + + 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(chan->index))) || + !(per & SUN8I_PWM_ENABLE(chan->index))) { + sun8i_pwm_set_enabled_locked(sun8i_chip, chan->index, false); + sun8i_pwm_set_bypass_locked(sun8i_chip, chan->index, true); + sun8i_pwm_set_enabled_locked(sun8i_chip, chan->index, true); + } + + return 0; +} + +static void sun8i_pwm_bypass_disable(struct clk_hw *hw) +{ + struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = chan->chip; + + guard(spinlock_irqsave)(&sun8i_chip->lock); + if (chan->mode != SUN8I_PWM_MODE_CLK) + return; + + sun8i_pwm_set_enabled_locked(sun8i_chip, chan->index, false); + sun8i_pwm_set_bypass_locked(sun8i_chip, chan->index, false); +} + +static int sun8i_pwm_bypass_is_enabled(struct clk_hw *hw) +{ + struct sun8i_pwm_channel *chan = sun8i_pwm_channel_from_hw(hw); + struct sun8i_pwm_chip *sun8i_chip = chan->chip; + unsigned int pair = SUN8I_PWM_PAIR_IDX(chan->index); + bool enabled; + u32 val; + + guard(spinlock_irqsave)(&sun8i_chip->lock); + + val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(pair)); + enabled = (val & SUN8I_PWM_PCCR_GATE) && + (val & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(chan->index))); + + val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER); + enabled = enabled && (val & SUN8I_PWM_ENABLE(chan->index)); + + return enabled; +} + +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, + .is_enabled = sun8i_pwm_bypass_is_enabled, +}; + static void sun8i_pwm_put_rate(struct sun8i_pwm_chip *sun8i_chip, unsigned int idx) { @@ -342,8 +450,12 @@ static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) int ret; scoped_guard(spinlock_irqsave, &sun8i_chip->lock) { + if (chan->mode != SUN8I_PWM_MODE_NONE) + return -EBUSY; + was_enabled = sun8i_pwm_channel_is_enabled_locked(sun8i_chip, idx); + chan->mode = SUN8I_PWM_MODE_PWM; } chan->pair_clk = clk_hw_get_clk(parent, NULL); @@ -372,6 +484,8 @@ static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) clk_put(chan->pair_clk); err_clear_clock: chan->pair_clk = NULL; + scoped_guard(spinlock_irqsave, &sun8i_chip->lock) + chan->mode = SUN8I_PWM_MODE_NONE; return ret; } @@ -382,6 +496,9 @@ static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm]; scoped_guard(spinlock_irqsave, &sun8i_chip->lock) { + if (chan->mode != SUN8I_PWM_MODE_PWM) + return; + sun8i_pwm_set_enabled_locked(sun8i_chip, pwm->hwpwm, false); sun8i_pwm_set_bypass_locked(sun8i_chip, pwm->hwpwm, false); } @@ -390,6 +507,9 @@ static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(chan->pair_clk); clk_put(chan->pair_clk); chan->pair_clk = NULL; + + scoped_guard(spinlock_irqsave, &sun8i_chip->lock) + chan->mode = SUN8I_PWM_MODE_NONE; } static int sun8i_pwm_read_waveform(struct pwm_chip *chip, @@ -510,7 +630,8 @@ sun8i_pwm_pair_rate_constrained(struct sun8i_pwm_chip *sun8i_chip, unsigned int sibling = idx ^ 1; guard(spinlock_irqsave)(&sun8i_chip->lock); - return sun8i_chip->channels[sibling].rate_exclusive || + return sun8i_chip->channels[sibling].mode == SUN8I_PWM_MODE_CLK || + sun8i_chip->channels[sibling].rate_exclusive || sun8i_pwm_channel_is_enabled_locked(sun8i_chip, sibling); } @@ -984,6 +1105,42 @@ static int sun8i_pwm_register_pair_clocks(struct device *dev, return 0; } +/* Register the bypass clock for each channel. */ +static int sun8i_pwm_register_bypass_clocks(struct device *dev, + struct sun8i_pwm_chip *sun8i_chip) +{ + for (unsigned int i = 0; i < SUN8I_PWM_NPWM; i++) { + struct sun8i_pwm_channel *chan = &sun8i_chip->channels[i]; + struct clk_hw *parent = sun8i_chip->pairs[SUN8I_PWM_PAIR_IDX(i)].hw; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s#pwm-bypass%u", + dev_name(dev), i); + if (!name) + return -ENOMEM; + + chan->chip = sun8i_chip; + chan->index = i; + /* + * Protect the shared rate while prepared. Preserve unclaimed + * firmware outputs through CCF's unused-clock cleanup, as for + * PWM waveforms; only a consumer may turn them off. + */ + chan->bypass_hw.init = + CLK_HW_INIT_HW(name, parent, &sun8i_pwm_bypass_ops, + CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE | + CLK_IGNORE_UNUSED); + + 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); + } + + 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 @@ -1012,6 +1169,17 @@ sun8i_pwm_sanitize_disabled_pairs(struct sun8i_pwm_chip *sun8i_chip) sun8i_pwm_writel(sun8i_chip, per, SUN8I_PWM_PER); } +static struct clk_hw *sun8i_pwm_get_clk_hw(struct of_phandle_args *clkspec, + void *data) +{ + struct sun8i_pwm_chip *sun8i_chip = data; + + if (clkspec->args[0] >= SUN8I_PWM_NPWM) + return ERR_PTR(-EINVAL); + + return &sun8i_chip->channels[clkspec->args[0]].bypass_hw; +} + static int sun8i_pwm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1050,6 +1218,14 @@ static int sun8i_pwm_probe(struct platform_device *pdev) if (ret) return ret; + ret = sun8i_pwm_register_bypass_clocks(dev, sun8i_chip); + if (ret) + return ret; + + ret = devm_of_clk_add_hw_provider(dev, sun8i_pwm_get_clk_hw, sun8i_chip); + 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");