From nobody Mon Jun 8 06:36:13 2026 Received: from zg8tmja5ljk3lje4mi4ymjia.icoremail.net (zg8tmja5ljk3lje4mi4ymjia.icoremail.net [209.97.182.222]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C3EE730F803; Fri, 5 Jun 2026 08:23:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.97.182.222 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780647823; cv=none; b=IHwd6T4lJwpCC5OqrfGG0Q0gYd/k6+Pr6RmnOReTdYvulY1NJf4616VQn6GSn0xqqLJt5ZGTtI6ZaBxRAJj8hs9WjRoRfhV3u68+hw/9B19/lFX2IZXzgNstgbTB8p6FTD7M0RfEQFw1NJguHSFzTYqiytIBIBqSzM/TAfQNr8U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780647823; c=relaxed/simple; bh=oyxEFiegdcQoXI3pzVwGCBopEGhO0BpoDHG8Y4dAINM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=KBzXDYK4tDqy2f8GjtYTFpa0gc2/Z3DkQ1EqxnunC6YBJGlL6itvoKVFxJ1CTnyAnsMyS5zSmfNJrstz+/dL29X+wywzN+s7k111upSpAUzUhzxhNtJuND9NRAuCQKwbrDFvalRxk1dHDOQY+DVixYk5QBM8ov8fEoAeP6YbzEY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=eswincomputing.com; spf=pass smtp.mailfrom=eswincomputing.com; arc=none smtp.client-ip=209.97.182.222 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=eswincomputing.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=eswincomputing.com Received: from E0005152DT.eswin.cn (unknown [10.12.96.41]) by app2 (Coremail) with SMTP id TQJkCgBHXqF5hyJqVVQiAA--.3400S2; Fri, 05 Jun 2026 16:23:29 +0800 (CST) From: dongxuyang@eswincomputing.com To: ukleinek@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, ben-linux@fluff.org, ben.dooks@codethink.co.uk, p.zabel@pengutronix.de, linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Cc: ningyu@eswincomputing.com, linmin@eswincomputing.com, xuxiang@eswincomputing.com, wangguosheng@eswincomputing.com, pinkesh.vaghela@einfochips.com, Xuyang Dong Subject: [PATCH v7 1/2] dt-bindings: pwm: dwc: Add eswin compatible and resets property Date: Fri, 5 Jun 2026 16:23:18 +0800 Message-Id: <20260605082318.1599-1-dongxuyang@eswincomputing.com> X-Mailer: git-send-email 2.31.1.windows.1 In-Reply-To: <20260605082242.1541-1-dongxuyang@eswincomputing.com> References: <20260605082242.1541-1-dongxuyang@eswincomputing.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: TQJkCgBHXqF5hyJqVVQiAA--.3400S2 X-Coremail-Antispam: 1UD129KBjvJXoW7Ww4fWrW8ZFykXw1xurW7Arb_yoW5Jr1UpF W3AFWvqr1fJr17Xw4rJF18CF13Xa95JFW3Gr1Iq3yYk39Fqa1UtFW5Kr15AFW5Ar4IvrW3 Wa93ur13A3Wjvr7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUBv14x267AKxVW8JVW5JwAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2ocxC64kIII0Yj41l84x0c7CEw4AK67xGY2AK02 1l84ACjcxK6xIIjxv20xvE14v26w1j6s0DM28EF7xvwVC0I7IYx2IY6xkF7I0E14v26r4U JVWxJr1l84ACjcxK6I8E87Iv67AKxVW0oVCq3wA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_Gc CE3s1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2WlYx0E 2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r4j6F4UMcvjeVCFs4IE7xkEbVWUJV W8JwACjcxG0xvY0x0EwIxGrwACjI8F5VA0II8E6IAqYI8I648v4I1lFIxGxcIEc7CjxVA2 Y2ka0xkIwI1lw4CEc2x0rVAKj4xxMxkF7I0En4kS14v26r1q6r43MxkIecxEwVCm-wCF04 k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18 MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr4 1lIxAIcVC0I7IYx2IY67AKxVWUJVWUCwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l IxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r1j6r4UMIIF0xvEx4 A2jsIEc7CjxVAFwI0_Gr0_Gr1UYxBIdaVFxhVjvjDU0xZFpf9x0JUBSoAUUUUU= X-CM-SenderInfo: pgrqw5xx1d0w46hv4xpqfrz1xxwl0woofrz/ Content-Type: text/plain; charset="utf-8" From: Xuyang Dong EIC7700 use DesignWare IP for PWM controllers. Add ESWIN EIC7700 support in snps,dw-apb-timers-pwm2.yaml. The DesignWare PWM includes separate reset signals dedicated to each clock domain: The presetn signal resets logic in pclk domain. The timer_N_resetn signal resets logic in the timer_N_clk domain. The resets are active-low. The generic snps,dw-apb-timers-pwm2 binding allows one or two optional reset lines depending on SoC integration. On EIC7700, the presetn and timer_N_resetn inputs are physically tied to a single reset line, therefore exactly one reset is required. Signed-off-by: Xuyang Dong --- .../bindings/pwm/snps,dw-apb-timers-pwm2.yaml | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.= yaml b/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml index 7523a89a1773..a4b7929f2e05 100644 --- a/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml +++ b/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml @@ -20,12 +20,11 @@ description: instead of having to encode the IP version number in the device tree compatible. =20 -allOf: - - $ref: pwm.yaml# - properties: compatible: - const: snps,dw-apb-timers-pwm2 + enum: + - snps,dw-apb-timers-pwm2 + - eswin,eic7700-pwm =20 reg: maxItems: 1 @@ -43,6 +42,12 @@ properties: - const: bus - const: timer =20 + resets: + minItems: 1 + items: + - description: Interface bus reset + - description: PWM timer logic reset + snps,pwm-number: $ref: /schemas/types.yaml#/definitions/uint32 description: The number of PWM channels configured for this instance @@ -54,6 +59,22 @@ required: - clocks - clock-names =20 +allOf: + - $ref: pwm.yaml# + + - if: + properties: + compatible: + contains: + const: eswin,eic7700-pwm + then: + properties: + resets: + minItems: 1 + maxItems: 1 + required: + - resets + additionalProperties: false =20 examples: @@ -65,3 +86,12 @@ examples: clocks =3D <&bus>, <&timer>; clock-names =3D "bus", "timer"; }; + - | + pwm@50818000 { + compatible =3D "eswin,eic7700-pwm"; + reg =3D <0x50818000 0x4000>; + #pwm-cells =3D <3>; + clocks =3D <&bus>, <&timer>; + clock-names =3D "bus", "timer"; + resets =3D <&reset>; + }; --=20 2.34.1 From nobody Mon Jun 8 06:36:13 2026 Received: from zg8tmtyylji0my4xnjeumjiw.icoremail.net (zg8tmtyylji0my4xnjeumjiw.icoremail.net [162.243.161.220]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 5756830F803; Fri, 5 Jun 2026 08:25:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.243.161.220 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780647904; cv=none; b=kIM2qjTy+Kk/47Zcsrh1VfbFreAewNTu7uVeOBxkAqFR2ZzNQ8l4QlSp8gz4K4kUZPW4bFXLyEuVapc3OlV4JVwTUoPB2vLBhjobfr82XbHQYxu9veG5LFwSU6nfDv/fO/uZRS37gopnDr3RMD1HFeRhIlsv68j8V5jr/YGtZuQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780647904; c=relaxed/simple; bh=YWNhVXCk6B4/VpOK7v9wyAYTeqo8cRzxV3i5q+EoJEc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=N2a2f1uTxsQYvfBiLz8u4YZaFVjpwblZPhLxHWAf/Cbjjcg9IZ8j9mqrtirWTAVL3HVtUuNdmA+4lYsPAkzasdCHmaL90RGo16pu5kv7J/qE8E9safkxQqYbWQNN76x4wcDBOn+bub+acDnbRWaVaVxRacHaUi7MEovRP+zWHM4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=eswincomputing.com; spf=pass smtp.mailfrom=eswincomputing.com; arc=none smtp.client-ip=162.243.161.220 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=eswincomputing.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=eswincomputing.com Received: from E0005152DT.eswin.cn (unknown [10.12.96.41]) by app2 (Coremail) with SMTP id TQJkCgB3i57KhyJqlVQiAA--.19730S2; Fri, 05 Jun 2026 16:24:49 +0800 (CST) From: Xuyang Dong To: ukleinek@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, ben-linux@fluff.org, ben.dooks@codethink.co.uk, p.zabel@pengutronix.de, linux-pwm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Cc: ningyu@eswincomputing.com, linmin@eswincomputing.com, xuxiang@eswincomputing.com, wangguosheng@eswincomputing.com, pinkesh.vaghela@einfochips.com, Xuyang Dong Subject: [PATCH v7 2/2] pwm: dwc: add of/platform support Date: Fri, 5 Jun 2026 16:24:39 +0800 Message-Id: <20260605082439.1801-1-dongxuyang@eswincomputing.com> X-Mailer: git-send-email 2.31.1.windows.1 In-Reply-To: <20260605082242.1541-1-dongxuyang@eswincomputing.com> References: <20260605082242.1541-1-dongxuyang@eswincomputing.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: TQJkCgB3i57KhyJqlVQiAA--.19730S2 X-Coremail-Antispam: 1UD129KBjvAXoW3ur1rZF1Utr48XFW3uryxuFg_yoW8ArW5Xo WfKr1fXw18K3Z3J392ka47Kayjvw4kt34fur1rWF4DC3Z8Zw15AFWUK34Ygw1Sqw1YyFWx Ar4xXr1fAF4fJw18n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYl7AC8VAFwI0_Gr0_Xr1l1xkIjI8I6I8E6xAIw20EY4v20xva j40_Wr0E3s1l1IIY67AEw4v_Jr0_Jr4l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxSw2 x7M28EF7xvwVC0I7IYx2IY67AKxVWDJVCq3wA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxVW8 Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_GcCE3s1l84ACjcxK6I8E87Iv6xkF7I0E14v26r xl6s0DM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F40Ex7xfMcIj 6xIIjxv20xvE14v26r1Y6r17McIj6I8E87Iv67AKxVW8JVWxJwAm72CE4IkC6x0Yz7v_Jr 0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYxC7M4IIrI8v6xkF7I0E 8cxan2IY04v7MxkF7I0En4kS14v26r1q6r43MxkIecxEwVCm-wCF04k20xvY0x0EwIxGrw CFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I3I0E7480Y4vE 14v26r106r1rMI8E67AF67kF1VAFwI0_GFv_WrylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2 IY67AKxVWUJVWUCwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1lIxAIcVCF04k26cxK x2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r1j6r4UMIIF0xvEx4A2jsIEc7CjxVAFwI 0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuYvjfU5iihUUUUU X-CM-SenderInfo: pgrqw5xx1d0w46hv4xpqfrz1xxwl0woofrz/ Content-Type: text/plain; charset="utf-8" The dwc pwm controller can be used in non-PCI systems, so allow either platform or OF based probing. The controller is reset only when no PWM channel is enabled. Otherwise, clocks are enabled and the runtime PM state is updated to reflect the active hardware configuration. Co-developed-by: Ben Dooks Signed-off-by: Ben Dooks Signed-off-by: Xiang Xu Signed-off-by: Guosheng Wang Signed-off-by: Xuyang Dong --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-dwc-core.c | 101 ++++++++--- drivers/pwm/pwm-dwc-of.c | 346 +++++++++++++++++++++++++++++++++++++ drivers/pwm/pwm-dwc.h | 25 ++- 5 files changed, 454 insertions(+), 29 deletions(-) create mode 100644 drivers/pwm/pwm-dwc-of.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376..50aea24b6168 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -249,6 +249,16 @@ config PWM_DWC To compile this driver as a module, choose M here: the module will be called pwm-dwc. =20 +config PWM_DWC_OF + tristate "DesignWare PWM Controller (OF bus)" + depends on HAS_IOMEM && (OF || COMPILE_TEST) + select PWM_DWC_CORE + help + PWM driver for Synopsys DWC PWM Controller on an OF bus or + a platform bus. + To compile this driver as a module, choose M here: the module + will be called pwm-dwc-of. + config PWM_EP93XX tristate "Cirrus Logic EP93xx PWM support" depends on ARCH_EP93XX || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025..470411a7e5ea 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_CRC) +=3D pwm-crc.o obj-$(CONFIG_PWM_CROS_EC) +=3D pwm-cros-ec.o obj-$(CONFIG_PWM_DWC_CORE) +=3D pwm-dwc-core.o obj-$(CONFIG_PWM_DWC) +=3D pwm-dwc.o +obj-$(CONFIG_PWM_DWC_OF) +=3D pwm-dwc-of.o obj-$(CONFIG_PWM_EP93XX) +=3D pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) +=3D pwm-fsl-ftm.o obj-$(CONFIG_PWM_GPIO) +=3D pwm-gpio.o diff --git a/drivers/pwm/pwm-dwc-core.c b/drivers/pwm/pwm-dwc-core.c index 6dabec93a3c6..a6de05e27321 100644 --- a/drivers/pwm/pwm-dwc-core.c +++ b/drivers/pwm/pwm-dwc-core.c @@ -12,6 +12,7 @@ #define DEFAULT_SYMBOL_NAMESPACE "dwc_pwm" =20 #include +#include #include #include #include @@ -44,21 +45,52 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dw= c, u32 high; u32 low; =20 - /* - * Calculate width of low and high period in terms of input clock - * periods and check are the result within HW limits between 1 and - * 2^32 periods. - */ - tmp =3D DIV_ROUND_CLOSEST_ULL(state->duty_cycle, dwc->clk_ns); - if (tmp < 1 || tmp > (1ULL << 32)) - return -ERANGE; - low =3D tmp - 1; - - tmp =3D DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle, - dwc->clk_ns); - if (tmp < 1 || tmp > (1ULL << 32)) - return -ERANGE; - high =3D tmp - 1; + if (dwc->clk) + dwc->clk_rate =3D clk_get_rate(dwc->clk); + + if (dwc->features & DWC_TIM_CTRL_0N100PWM_EN) { + /* + * Calculate width of low and high period in terms of input + * clock periods and check are the result within HW limits + * between 0 and 2^32 periods. + */ + tmp =3D state->duty_cycle * dwc->clk_rate; + tmp =3D DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC); + if (tmp >=3D (1ULL << 32)) + return -ERANGE; + + if (pwm->args.polarity =3D=3D PWM_POLARITY_INVERSED) + high =3D tmp; + else + low =3D tmp; + + tmp =3D (state->period - state->duty_cycle) * dwc->clk_rate; + tmp =3D DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC); + if (tmp >=3D (1ULL << 32)) + return -ERANGE; + + if (pwm->args.polarity =3D=3D PWM_POLARITY_INVERSED) + low =3D tmp; + else + high =3D tmp; + } else { + /* + * Calculate width of low and high period in terms of input + * clock periods and check are the result within HW limits + * between 1 and 2^32 periods. + */ + tmp =3D state->duty_cycle * dwc->clk_rate; + tmp =3D DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC); + if (tmp < 1 || tmp > (1ULL << 32)) + return -ERANGE; + low =3D tmp - 1; + + tmp =3D (state->period - state->duty_cycle) * dwc->clk_rate; + tmp =3D DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC); + if (tmp < 1 || tmp > (1ULL << 32)) + return -ERANGE; + high =3D tmp - 1; + } =20 /* * Specification says timer usage flow is to disable timer, then @@ -74,6 +106,7 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc, * width of low period and latter the width of high period in terms * multiple of input clock periods: * Width =3D ((Count + 1) * input clock period). + * Width =3D (Count * input clock period) : supported 0% and 100%. */ dwc_pwm_writel(dwc, low, DWC_TIM_LD_CNT(pwm->hwpwm)); dwc_pwm_writel(dwc, high, DWC_TIM_LD_CNT2(pwm->hwpwm)); @@ -85,6 +118,9 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc, * periods are set by Load Count registers. */ ctrl =3D DWC_TIM_CTRL_MODE_USER | DWC_TIM_CTRL_PWM; + if (dwc->features & DWC_TIM_CTRL_0N100PWM_EN) + ctrl |=3D DWC_TIM_CTRL_0N100PWM_EN; + dwc_pwm_writel(dwc, ctrl, DWC_TIM_CTRL(pwm->hwpwm)); =20 /* @@ -121,11 +157,17 @@ static int dwc_pwm_get_state(struct pwm_chip *chip, s= truct pwm_device *pwm, struct pwm_state *state) { struct dwc_pwm *dwc =3D to_dwc_pwm(chip); + unsigned long clk_rate; u64 duty, period; u32 ctrl, ld, ld2; =20 pm_runtime_get_sync(pwmchip_parent(chip)); =20 + if (dwc->clk) + dwc->clk_rate =3D clk_get_rate(dwc->clk); + + clk_rate =3D dwc->clk_rate; + ctrl =3D dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm->hwpwm)); ld =3D dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(pwm->hwpwm)); ld2 =3D dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(pwm->hwpwm)); @@ -137,17 +179,32 @@ static int dwc_pwm_get_state(struct pwm_chip *chip, s= truct pwm_device *pwm, * based on the timer load-count only. */ if (ctrl & DWC_TIM_CTRL_PWM) { - duty =3D (ld + 1) * dwc->clk_ns; - period =3D (ld2 + 1) * dwc->clk_ns; - period +=3D duty; + if (dwc->features & DWC_TIM_CTRL_0N100PWM_EN) { + if (pwm->args.polarity =3D=3D PWM_POLARITY_INVERSED) + duty =3D ld2; + else + duty =3D ld; + period =3D (u64)ld + ld2; + } else { + duty =3D ld + 1; + period =3D ld2 + 1; + period +=3D duty; + } } else { - duty =3D (ld + 1) * dwc->clk_ns; + duty =3D ld + 1; period =3D duty * 2; } =20 state->polarity =3D PWM_POLARITY_INVERSED; - state->period =3D period; - state->duty_cycle =3D duty; + /* + * If the ld register is at its maximum value. The duty value is + * 4,294,967,295 (0xFFFF FFFF). The product (duty * NSEC_PER_SEC) + * is guaranteed to be less than 2^64. + */ + duty *=3D NSEC_PER_SEC; + period *=3D NSEC_PER_SEC; + state->period =3D DIV_ROUND_UP_ULL(period, clk_rate); + state->duty_cycle =3D DIV_ROUND_UP_ULL(duty, clk_rate); =20 pm_runtime_put_sync(pwmchip_parent(chip)); =20 @@ -169,7 +226,7 @@ struct pwm_chip *dwc_pwm_alloc(struct device *dev) return chip; dwc =3D to_dwc_pwm(chip); =20 - dwc->clk_ns =3D 10; + dwc->clk_rate =3D NSEC_PER_SEC / 10; chip->ops =3D &dwc_pwm_ops; =20 return chip; diff --git a/drivers/pwm/pwm-dwc-of.c b/drivers/pwm/pwm-dwc-of.c new file mode 100644 index 000000000000..d8b1606e51af --- /dev/null +++ b/drivers/pwm/pwm-dwc-of.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DesignWare PWM Controller driver OF + * + * Copyright (C) 2026 SiFive, Inc. + */ + +#define DEFAULT_SYMBOL_NAMESPACE "dwc_pwm_of" + +#include +#include +#include +#include +#include + +#include "pwm-dwc.h" + +struct dwc_pwm_plat_data { + bool reset_required; +}; + +static int dwc_pwm_plat_probe(struct platform_device *pdev) +{ + const struct dwc_pwm_plat_data *pdata; + struct device *dev =3D &pdev->dev; + struct dwc_pwm_drvdata *data; + u32 ctrl[DWC_TIMERS_TOTAL]; + struct pwm_chip *chip; + struct dwc_pwm *dwc; + bool pwm_en =3D false; + u32 nr_pwm, tim_id; + unsigned int i; + int ret; + + data =3D devm_kzalloc(dev, struct_size(data, chips, 1), GFP_KERNEL); + if (!data) + return -ENOMEM; + + chip =3D dwc_pwm_alloc(dev); + if (IS_ERR(chip)) + return dev_err_probe(dev, PTR_ERR(chip), + "failed to alloc pwm\n"); + + dwc =3D to_dwc_pwm(chip); + + dwc->base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dwc->base)) + return PTR_ERR(dwc->base); + + if (!device_property_read_u32(dev, "snps,pwm-number", &nr_pwm)) { + if (nr_pwm > DWC_TIMERS_TOTAL) + dev_warn(dev, "too many PWMs (%d), capping at %d\n", + nr_pwm, chip->npwm); + else + chip->npwm =3D nr_pwm; + } + + dwc->bus_clk =3D devm_clk_get(dev, "bus"); + if (IS_ERR(dwc->bus_clk)) + return dev_err_probe(dev, PTR_ERR(dwc->bus_clk), + "failed to get bus clock\n"); + + dwc->clk =3D devm_clk_get(dev, "timer"); + if (IS_ERR(dwc->clk)) + return dev_err_probe(dev, PTR_ERR(dwc->clk), + "failed to get timer clock\n"); + + ret =3D devm_clk_rate_exclusive_get(dev, dwc->clk); + if (ret) + return dev_err_probe(dev, ret, + "failed to get exclusive rate\n"); + + dwc->clk_rate =3D clk_get_rate(dwc->clk); + + pdata =3D device_get_match_data(dev); + if (pdata && pdata->reset_required) + dwc->rst =3D devm_reset_control_get_exclusive(dev, NULL); + else + dwc->rst =3D devm_reset_control_array_get_optional_exclusive(dev); + + if (IS_ERR(dwc->rst)) + return dev_err_probe(dev, PTR_ERR(dwc->rst), + "failed to get reset control\n"); + + ret =3D clk_prepare_enable(dwc->bus_clk); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable bus clock\n"); + + ret =3D clk_prepare_enable(dwc->clk); + if (ret) { + dev_err(dev, "failed to enable timer clock\n"); + goto disable_busclk; + } + + /* + * Check all channels to see if any channel is enabled. + * Read the control register of each channel and extract the enable bit + */ + for (i =3D 0; i < chip->npwm; i++) { + ctrl[i] =3D dwc_pwm_readl(dwc, DWC_TIM_CTRL(i)) & DWC_TIM_CTRL_EN; + if (ctrl[i]) + pwm_en =3D true; + } + + /* Only issue reset when all channels are disabled */ + if (!pwm_en) { + ret =3D reset_control_reset(dwc->rst); + if (ret) { + dev_err(dev, "failed to reset\n"); + goto disable_clk; + } + } + + /* init PWM feature */ + dwc->features =3D 0; + /* + * Support for 0% and 100% duty cycle mode was added in version 2.11a + * and later. + */ + tim_id =3D dwc_pwm_readl(dwc, DWC_TIMERS_COMP_VERSION); + if (tim_id >=3D DWC_TIM_VERSION_ID_2_11A) + dwc->features |=3D DWC_TIM_CTRL_0N100PWM_EN; + + ret =3D devm_pwmchip_add(dev, chip); + if (ret) { + dev_err(dev, "failed to add pwm chip\n"); + goto reset_assert; + } + + data->chips[0] =3D chip; + dev_set_drvdata(dev, data); + + /* + * If any PWM channel is enabled, mark device active and hold runtime PM + * references for each enabled channel. Otherwise, gate the clocks. + */ + if (pwm_en) { + pm_runtime_set_active(dev); + for (i =3D 0; i < chip->npwm; i++) { + if (ctrl[i]) + pm_runtime_get_noresume(dev); + } + } else { + clk_disable_unprepare(dwc->clk); + clk_disable_unprepare(dwc->bus_clk); + } + + pm_runtime_enable(dev); + + return 0; + +reset_assert: + reset_control_assert(dwc->rst); +disable_clk: + clk_disable_unprepare(dwc->clk); +disable_busclk: + clk_disable_unprepare(dwc->bus_clk); + + return ret; +} + +static void dwc_pwm_plat_remove(struct platform_device *pdev) +{ + struct dwc_pwm_drvdata *data =3D platform_get_drvdata(pdev); + struct pwm_chip *chip =3D data->chips[0]; + struct dwc_pwm *dwc =3D to_dwc_pwm(chip); + bool pwm_en =3D false; + unsigned int idx; + bool pm_flags; + + /* + * Resume the device if it is runtime suspended to allow + * safe register access. + */ + pm_flags =3D pm_runtime_status_suspended(&pdev->dev); + if (pm_flags) + pm_runtime_get_sync(&pdev->dev); + + for (idx =3D 0; idx < chip->npwm; idx++) { + if (dwc_pwm_readl(dwc, DWC_TIM_CTRL(idx)) & DWC_TIM_CTRL_EN) { + pwm_en =3D true; + pm_runtime_put_noidle(&pdev->dev); + } + } + + /* + * Re-suspend the device if it was runtime suspended prior to + * the register access. + */ + if (pm_flags) + pm_runtime_put_sync(&pdev->dev); + + if (pwm_en) { + clk_disable_unprepare(dwc->clk); + clk_disable_unprepare(dwc->bus_clk); + } + + pm_runtime_disable(&pdev->dev); + reset_control_assert(dwc->rst); +} + +static int dwc_pwm_runtime_suspend(struct device *dev) +{ + struct dwc_pwm_drvdata *data =3D dev_get_drvdata(dev); + struct pwm_chip *chip =3D data->chips[0]; + struct dwc_pwm *dwc =3D to_dwc_pwm(chip); + + clk_disable_unprepare(dwc->clk); + clk_disable_unprepare(dwc->bus_clk); + + return 0; +} + +static int dwc_pwm_runtime_resume(struct device *dev) +{ + struct dwc_pwm_drvdata *data =3D dev_get_drvdata(dev); + struct pwm_chip *chip =3D data->chips[0]; + struct dwc_pwm *dwc =3D to_dwc_pwm(chip); + int ret; + + ret =3D clk_prepare_enable(dwc->bus_clk); + if (ret) { + dev_err(dev, "failed to enable bus clock: %d\n", ret); + return ret; + } + + ret =3D clk_prepare_enable(dwc->clk); + if (ret) { + dev_err(dev, "failed to enable timer clock: %d\n", ret); + clk_disable_unprepare(dwc->bus_clk); + return ret; + } + + return 0; +} + +static int dwc_pwm_suspend(struct device *dev) +{ + struct dwc_pwm_drvdata *data =3D dev_get_drvdata(dev); + struct pwm_chip *chip =3D data->chips[0]; + struct dwc_pwm *dwc =3D to_dwc_pwm(chip); + unsigned int idx; + int ret; + + if (pm_runtime_status_suspended(dev)) { + ret =3D dwc_pwm_runtime_resume(dev); + if (ret) + return ret; + } + + for (idx =3D 0; idx < chip->npwm; idx++) { + if (chip->pwms[idx].state.enabled) + return -EBUSY; + + dwc->ctx[idx].cnt =3D dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(idx)); + dwc->ctx[idx].cnt2 =3D dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(idx)); + dwc->ctx[idx].ctrl =3D dwc_pwm_readl(dwc, DWC_TIM_CTRL(idx)); + } + + ret =3D dwc_pwm_runtime_suspend(dev); + if (ret) + return ret; + + return 0; +} + +static int dwc_pwm_resume(struct device *dev) +{ + struct dwc_pwm_drvdata *data =3D dev_get_drvdata(dev); + struct pwm_chip *chip =3D data->chips[0]; + struct dwc_pwm *dwc =3D to_dwc_pwm(chip); + unsigned int idx; + bool pm_flags; + int ret; + + /* Check if device was runtime suspended before system resume */ + pm_flags =3D pm_runtime_status_suspended(dev); + if (pm_flags) { + /* + * Use PM framework to resume device + * (calls dwc_pwm_runtime_resume) + */ + ret =3D pm_runtime_get_sync(dev); + if (ret < 0) + return ret; + } else { + /* + * Device was active, but clocks might be off after system sleep + * Call runtime_resume directly to restore hardware state + */ + ret =3D dwc_pwm_runtime_resume(dev); + if (ret) + return ret; + } + + for (idx =3D 0; idx < chip->npwm; idx++) { + dwc_pwm_writel(dwc, dwc->ctx[idx].cnt, DWC_TIM_LD_CNT(idx)); + dwc_pwm_writel(dwc, dwc->ctx[idx].cnt2, DWC_TIM_LD_CNT2(idx)); + dwc_pwm_writel(dwc, dwc->ctx[idx].ctrl, DWC_TIM_CTRL(idx)); + } + + if (pm_flags) { + /* Balance the refcount taken by pm_runtime_get_sync + * if it was used + */ + ret =3D pm_runtime_put_sync(dev); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct dev_pm_ops dwc_pwm_pm_ops =3D { + RUNTIME_PM_OPS(dwc_pwm_runtime_suspend, dwc_pwm_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(dwc_pwm_suspend, dwc_pwm_resume) +}; + +static const struct dwc_pwm_plat_data pwm_eic7700_pdata =3D { + .reset_required =3D true, +}; + +static const struct of_device_id dwc_pwm_dt_ids[] =3D { + { .compatible =3D "snps,dw-apb-timers-pwm2" }, + { .compatible =3D "eswin,eic7700-pwm", .data =3D &pwm_eic7700_pdata }, + { } +}; +MODULE_DEVICE_TABLE(of, dwc_pwm_dt_ids); + +static struct platform_driver dwc_pwm_plat_driver =3D { + .driver =3D { + .name =3D "dwc-pwm", + .pm =3D pm_ptr(&dwc_pwm_pm_ops), + .of_match_table =3D dwc_pwm_dt_ids, + }, + .probe =3D dwc_pwm_plat_probe, + .remove =3D dwc_pwm_plat_remove, +}; + +module_platform_driver(dwc_pwm_plat_driver); + +MODULE_ALIAS("platform:dwc-pwm-of"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("DesignWare PWM Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-dwc.h b/drivers/pwm/pwm-dwc.h index 1562594e7f85..75f7c2d031c4 100644 --- a/drivers/pwm/pwm-dwc.h +++ b/drivers/pwm/pwm-dwc.h @@ -26,12 +26,19 @@ MODULE_IMPORT_NS("dwc_pwm"); #define DWC_TIMERS_TOTAL 8 =20 /* Timer Control Register */ -#define DWC_TIM_CTRL_EN BIT(0) -#define DWC_TIM_CTRL_MODE BIT(1) -#define DWC_TIM_CTRL_MODE_FREE (0 << 1) -#define DWC_TIM_CTRL_MODE_USER (1 << 1) -#define DWC_TIM_CTRL_INT_MASK BIT(2) -#define DWC_TIM_CTRL_PWM BIT(3) +#define DWC_TIM_CTRL_EN BIT(0) +#define DWC_TIM_CTRL_MODE BIT(1) +#define DWC_TIM_CTRL_MODE_FREE (0 << 1) +#define DWC_TIM_CTRL_MODE_USER BIT(1) +#define DWC_TIM_CTRL_INT_MASK BIT(2) +#define DWC_TIM_CTRL_PWM BIT(3) +#define DWC_TIM_CTRL_0N100PWM_EN BIT(4) + +/* + * The version 2.11a and later add "Pulse Width Modulation with + * 0% and 100% Duty Cycle". + */ +#define DWC_TIM_VERSION_ID_2_11A 0x3231312a =20 struct dwc_pwm_info { unsigned int nr; @@ -52,8 +59,12 @@ struct dwc_pwm_ctx { =20 struct dwc_pwm { void __iomem *base; - unsigned int clk_ns; + struct clk *bus_clk; + struct clk *clk; + unsigned long clk_rate; + struct reset_control *rst; struct dwc_pwm_ctx ctx[DWC_TIMERS_TOTAL]; + u32 features; }; =20 static inline struct dwc_pwm *to_dwc_pwm(struct pwm_chip *chip) --=20 2.34.1