From 4bb65fe6ba2e6ea13d726c2860122eac746b75c8 Mon Sep 17 00:00:00 2001 From: Hal Feng Date: Fri, 15 May 2026 13:47:20 +0800 Subject: [PATCH 1/3] dt-bindings: pwm: opencores: Drop starfive compatibles and update maintainers Each of the StarFive JH7100/JH7110/JH8100 SoCs has 8 OpenCores PTC IP cores. One OpenCores PTC IP core can output one PWM channel. The only difference among them is the register base address. There is no need to add starfive compatibles to distinguish them. I will maintain the pwm module in place of William. Signed-off-by: Hal Feng Signed-off-by: Linux RISC-V bot --- .../devicetree/bindings/pwm/opencores,pwm.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/opencores,pwm.yaml b/Documentation/devicetree/bindings/pwm/opencores,pwm.yaml index 52a59d245cdb13..834fb17ec595e1 100644 --- a/Documentation/devicetree/bindings/pwm/opencores,pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/opencores,pwm.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: OpenCores PWM controller maintainers: - - William Qiu + - Hal Feng description: The OpenCores PTC ip core contains a PWM controller. When operating in PWM @@ -20,10 +20,6 @@ allOf: properties: compatible: items: - - enum: - - starfive,jh7100-pwm - - starfive,jh7110-pwm - - starfive,jh8100-pwm - const: opencores,pwm-v1 reg: @@ -48,8 +44,8 @@ additionalProperties: false examples: - | pwm@12490000 { - compatible = "starfive,jh7110-pwm", "opencores,pwm-v1"; - reg = <0x12490000 0x10000>; + compatible = "opencores,pwm-v1"; + reg = <0x12490000 0x10>; clocks = <&clkgen 181>; resets = <&rstgen 109>; #pwm-cells = <3>; From 95da73e4415ea21c7286694e816916296adba244 Mon Sep 17 00:00:00 2001 From: Hal Feng Date: Fri, 15 May 2026 13:47:21 +0800 Subject: [PATCH 2/3] riscv: dts: starfive: Correct pwm nodes Each of the StarFive JH7100/JH7110 SoCs has 8 OpenCores PTC IP cores. One OpenCores PTC IP core can output one PWM channel. Signed-off-by: Hal Feng Signed-off-by: Linux RISC-V bot --- .../boot/dts/starfive/jh7100-common.dtsi | 28 ++++++-- arch/riscv/boot/dts/starfive/jh7100.dtsi | 69 ++++++++++++++++++- .../boot/dts/starfive/jh7110-common.dtsi | 27 ++++++-- .../boot/dts/starfive/jh7110-milkv-mars.dts | 6 +- .../dts/starfive/jh7110-milkv-marscm.dtsi | 6 +- .../dts/starfive/jh7110-pine64-star64.dts | 6 +- .../jh7110-starfive-visionfive-2-lite.dtsi | 6 +- .../jh7110-starfive-visionfive-2.dtsi | 6 +- arch/riscv/boot/dts/starfive/jh7110.dtsi | 69 ++++++++++++++++++- 9 files changed, 200 insertions(+), 23 deletions(-) diff --git a/arch/riscv/boot/dts/starfive/jh7100-common.dtsi b/arch/riscv/boot/dts/starfive/jh7100-common.dtsi index ae1a6aeb0aeaa1..85106545090ea9 100644 --- a/arch/riscv/boot/dts/starfive/jh7100-common.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7100-common.dtsi @@ -199,13 +199,23 @@ }; }; - pwm_pins: pwm-0 { - pwm-pins { + pwm0_pins: pwm0-0 { + pwm0-pins { pinmux = , - ; + bias-disable; + drive-strength = <35>; + input-disable; + input-schmitt-disable; + slew-rate = <0>; + }; + }; + + pwm1_pins: pwm1-0 { + pwm1-pins { + pinmux = ; @@ -359,9 +369,15 @@ clock-frequency = <27000000>; }; -&pwm { +&pwm0 { + pinctrl-names = "default"; + pinctrl-0 = <&pwm0_pins>; + status = "okay"; +}; + +&pwm1 { pinctrl-names = "default"; - pinctrl-0 = <&pwm_pins>; + pinctrl-0 = <&pwm1_pins>; status = "okay"; }; diff --git a/arch/riscv/boot/dts/starfive/jh7100.dtsi b/arch/riscv/boot/dts/starfive/jh7100.dtsi index 7de0732b8eabe4..4629e9747307d6 100644 --- a/arch/riscv/boot/dts/starfive/jh7100.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi @@ -360,9 +360,72 @@ <&rstgen JH7100_RSTN_WDT>; }; - pwm: pwm@12490000 { - compatible = "starfive,jh7100-pwm", "opencores,pwm-v1"; - reg = <0x0 0x12490000 0x0 0x10000>; + pwm0: pwm@12490000 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12490000 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1: pwm@12490010 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12490010 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2: pwm@12490020 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12490020 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm3: pwm@12490030 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12490030 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm4: pwm@12498000 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12498000 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm5: pwm@12498010 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12498010 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm6: pwm@12498020 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12498020 0x0 0x10>; + clocks = <&clkgen JH7100_CLK_PWM_APB>; + resets = <&rstgen JH7100_RSTN_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm7: pwm@12498030 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x12498030 0x0 0x10>; clocks = <&clkgen JH7100_CLK_PWM_APB>; resets = <&rstgen JH7100_RSTN_PWM_APB>; #pwm-cells = <3>; diff --git a/arch/riscv/boot/dts/starfive/jh7110-common.dtsi b/arch/riscv/boot/dts/starfive/jh7110-common.dtsi index 8cfe8033305d80..5aa225b8bca8ab 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-common.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110-common.dtsi @@ -351,9 +351,14 @@ }; }; -&pwm { +&pwm0 { pinctrl-names = "default"; - pinctrl-0 = <&pwm_pins>; + pinctrl-0 = <&pwm0_pins>; +}; + +&pwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&pwm1_pins>; }; &spi0 { @@ -553,12 +558,22 @@ }; }; - pwm_pins: pwm-0 { - pwm-pins { + pwm0_pins: pwm0-0 { + pwm0-pins { pinmux = , - ; + bias-disable; + drive-strength = <12>; + input-disable; + input-schmitt-disable; + slew-rate = <0>; + }; + }; + + pwm1_pins: pwm1-0 { + pwm1-pins { + pinmux = ; bias-disable; diff --git a/arch/riscv/boot/dts/starfive/jh7110-milkv-mars.dts b/arch/riscv/boot/dts/starfive/jh7110-milkv-mars.dts index 21873612d99312..54013c70f4b4ee 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-milkv-mars.dts +++ b/arch/riscv/boot/dts/starfive/jh7110-milkv-mars.dts @@ -68,7 +68,11 @@ motorcomm,tx-clk-adj-enabled; }; -&pwm { +&pwm0 { + status = "okay"; +}; + +&pwm1 { status = "okay"; }; diff --git a/arch/riscv/boot/dts/starfive/jh7110-milkv-marscm.dtsi b/arch/riscv/boot/dts/starfive/jh7110-milkv-marscm.dtsi index 025471061d4391..31afac27b86de0 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-milkv-marscm.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110-milkv-marscm.dtsi @@ -87,7 +87,11 @@ motorcomm,tx-clk-adj-enabled; }; -&pwm { +&pwm0 { + status = "okay"; +}; + +&pwm1 { status = "okay"; }; diff --git a/arch/riscv/boot/dts/starfive/jh7110-pine64-star64.dts b/arch/riscv/boot/dts/starfive/jh7110-pine64-star64.dts index aec7ae3d1f5b4d..a9e82f25efdeca 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-pine64-star64.dts +++ b/arch/riscv/boot/dts/starfive/jh7110-pine64-star64.dts @@ -95,7 +95,11 @@ motorcomm,tx-clk-100-inverted; }; -&pwm { +&pwm0 { + status = "okay"; +}; + +&pwm1 { status = "okay"; }; diff --git a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-lite.dtsi b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-lite.dtsi index f8797a666dbf59..85b56a72dff7aa 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-lite.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2-lite.dtsi @@ -74,7 +74,11 @@ tx-internal-delay-ps = <1500>; }; -&pwm { +&pwm0 { + status = "okay"; +}; + +&pwm1 { status = "okay"; }; diff --git a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi index edc8f45881336e..35208f95cd3d78 100644 --- a/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110-starfive-visionfive-2.dtsi @@ -73,7 +73,11 @@ status = "okay"; }; -&pwm { +&pwm0 { + status = "okay"; +}; + +&pwm1 { status = "okay"; }; diff --git a/arch/riscv/boot/dts/starfive/jh7110.dtsi b/arch/riscv/boot/dts/starfive/jh7110.dtsi index 6e56e9d20bb064..e6b9b02bf8b2f1 100644 --- a/arch/riscv/boot/dts/starfive/jh7110.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7110.dtsi @@ -846,9 +846,72 @@ status = "disabled"; }; - pwm: pwm@120d0000 { - compatible = "starfive,jh7110-pwm", "opencores,pwm-v1"; - reg = <0x0 0x120d0000 0x0 0x10000>; + pwm0: pwm@120d0000 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d0000 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm1: pwm@120d0010 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d0010 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm2: pwm@120d0020 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d0020 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm3: pwm@120d0030 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d0030 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm4: pwm@120d8000 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d8000 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm5: pwm@120d8010 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d8010 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm6: pwm@120d8020 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d8020 0x0 0x10>; + clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; + resets = <&syscrg JH7110_SYSRST_PWM_APB>; + #pwm-cells = <3>; + status = "disabled"; + }; + + pwm7: pwm@120d8030 { + compatible = "opencores,pwm-v1"; + reg = <0x0 0x120d8030 0x0 0x10>; clocks = <&syscrg JH7110_SYSCLK_PWM_APB>; resets = <&syscrg JH7110_SYSRST_PWM_APB>; #pwm-cells = <3>; From f1a9c803b4f3deabe0172c187760957717e02694 Mon Sep 17 00:00:00 2001 From: Hal Feng Date: Fri, 15 May 2026 13:47:22 +0800 Subject: [PATCH 3/3] pwm: Add OpenCores PTC PWM driver Add PWM driver for OpenCores PTC IP core. Signed-off-by: Hal Feng Signed-off-by: Linux RISC-V bot --- MAINTAINERS | 6 + drivers/pwm/Kconfig | 12 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-ocores.c | 249 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+) create mode 100644 drivers/pwm/pwm-ocores.c diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd1638..4c2cec74322b3f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19984,6 +19984,12 @@ F: Documentation/i2c/busses/i2c-ocores.rst F: drivers/i2c/busses/i2c-ocores.c F: include/linux/platform_data/i2c-ocores.h +OPENCORES PWM DRIVER +M: Hal Feng +S: Supported +F: Documentation/devicetree/bindings/pwm/opencores,pwm.yaml +F: drivers/pwm/pwm-ocores.c + OPENRISC ARCHITECTURE M: Jonas Bonn M: Stefan Kristiansson diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 6f3147518376a0..dd7f3bf5c3ebc6 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -534,6 +534,18 @@ config PWM_NTXEC controller found in certain e-book readers designed by the original design manufacturer Netronix. +config PWM_OCORES + tristate "OpenCores PTC PWM support" + depends on HAS_IOMEM && OF + depends on COMMON_CLK + depends on ARCH_STARFIVE || COMPILE_TEST + help + PWM driver for OpenCores PTC IP core. + For details see https://opencores.org/projects/ptc. + + To compile this driver as a module, choose M here: the module + will be called pwm-ocores. + config PWM_OMAP_DMTIMER tristate "OMAP Dual-Mode Timer PWM support" depends on OF diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0dc0d2b69025db..2d47bad7bd7421 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_MICROCHIP_CORE) += pwm-microchip-core.o obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o +obj-$(CONFIG_PWM_OCORES) += pwm-ocores.o obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o diff --git a/drivers/pwm/pwm-ocores.c b/drivers/pwm/pwm-ocores.c new file mode 100644 index 00000000000000..fa6a34117cded6 --- /dev/null +++ b/drivers/pwm/pwm-ocores.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * OpenCores PTC PWM Driver + * + * https://opencores.org/projects/ptc + * + * Copyright (C) 2018-2026 StarFive Technology Co., Ltd. + * + * Limitations: + * - The hardware only supports inverted polarity. + * - The hardware minimum period / duty_cycle of PWM is (1 / pwm_apb clock frequency). + * - The hardware maximum period / duty_cycle of PWM is (U32_MAX / pwm_apb clock frequency). + * - The output is immediately set to low when the module is disabled. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define OCPWM_HRC 0x4 +#define OCPWM_LRC 0x8 +#define OCPWM_CTRL 0xC + +#define OCPWM_CTRL_EN BIT(0) +#define OCPWM_CTRL_OE BIT(3) +#define OCPWM_CTRL_RST BIT(7) + +struct ocores_pwm_device { + void __iomem *base; + struct clk *clk; + unsigned long clk_rate; + struct reset_control *rst; +}; + +static int ocores_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct ocores_pwm_device *ddata = pwmchip_get_drvdata(chip); + u32 period_data, duty_data, ctrl_data; + int ret; + + ret = pm_runtime_resume_and_get(pwmchip_parent(chip)); + if (ret < 0) + return ret; + + period_data = readl(ddata->base + OCPWM_LRC); + duty_data = readl(ddata->base + OCPWM_HRC); + ctrl_data = readl(ddata->base + OCPWM_CTRL); + + state->period = DIV_ROUND_UP_ULL((u64)period_data * NSEC_PER_SEC, ddata->clk_rate); + state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty_data * NSEC_PER_SEC, ddata->clk_rate); + if (state->duty_cycle > state->period) + state->duty_cycle = state->period; + + state->polarity = PWM_POLARITY_INVERSED; + state->enabled = (ctrl_data & OCPWM_CTRL_EN) ? true : false; + + pm_runtime_put(pwmchip_parent(chip)); + + return 0; +} + +static int ocores_pwm_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct ocores_pwm_device *ddata = pwmchip_get_drvdata(chip); + u64 period_data, duty_data; + int ret; + + if (state->polarity != PWM_POLARITY_INVERSED) + return -EINVAL; + + if (state->enabled) { + if (!pwm_is_enabled(pwm)) { + ret = pm_runtime_resume_and_get(pwmchip_parent(chip)); + if (ret < 0) + return ret; + } + } else { + if (pwm_is_enabled(pwm)) { + writel(0, ddata->base + OCPWM_CTRL); + pm_runtime_put(pwmchip_parent(chip)); + } + return 0; + } + + writel(0, ddata->base + OCPWM_CTRL); + writel(OCPWM_CTRL_RST, ddata->base + OCPWM_CTRL); + + period_data = mul_u64_u32_div(state->period, ddata->clk_rate, NSEC_PER_SEC); + if (period_data > U32_MAX) + period_data = U32_MAX; + + duty_data = mul_u64_u32_div(state->duty_cycle, ddata->clk_rate, NSEC_PER_SEC); + if (duty_data > U32_MAX) + duty_data = U32_MAX; + + writel(period_data, ddata->base + OCPWM_LRC); + writel(duty_data, ddata->base + OCPWM_HRC); + writel(OCPWM_CTRL_OE | OCPWM_CTRL_EN, ddata->base + OCPWM_CTRL); + + return 0; +} + +static const struct pwm_ops ocores_pwm_ops = { + .get_state = ocores_pwm_get_state, + .apply = ocores_pwm_apply, +}; + +static int ocores_pwm_runtime_suspend(struct device *dev) +{ + struct ocores_pwm_device *ddata = dev_get_drvdata(dev); + + clk_disable_unprepare(ddata->clk); + + return 0; +} + +static int ocores_pwm_runtime_resume(struct device *dev) +{ + struct ocores_pwm_device *ddata = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(ddata->clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable clock\n"); + + return 0; +} + +static const struct dev_pm_ops ocores_pwm_pm_ops = { + RUNTIME_PM_OPS(ocores_pwm_runtime_suspend, + ocores_pwm_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static void ocores_pwm_pm_disable(void *data) +{ + struct device *dev = data; + struct ocores_pwm_device *ddata = dev_get_drvdata(dev); + + pm_runtime_disable(dev); + + if (!pm_runtime_status_suspended(dev)) + ocores_pwm_runtime_suspend(dev); + + reset_control_assert(ddata->rst); +} + +static int ocores_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ocores_pwm_device *ddata; + struct pwm_chip *chip; + int ret; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*ddata)); + if (IS_ERR(chip)) + return -ENOMEM; + + chip->ops = &ocores_pwm_ops; + ddata = pwmchip_get_drvdata(chip); + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return dev_err_probe(dev, PTR_ERR(ddata->base), + "Failed to map IO resources\n"); + + ddata->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ddata->clk)) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "Failed to get clock\n"); + + ddata->clk_rate = clk_get_rate(ddata->clk); + if (!ddata->clk_rate || ddata->clk_rate > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, + "Invalid clock rate: %lu\n", ddata->clk_rate); + + ddata->rst = devm_reset_control_get_optional_shared(dev, NULL); + if (IS_ERR(ddata->rst)) + return dev_err_probe(dev, PTR_ERR(ddata->rst), + "Failed to get reset\n"); + + platform_set_drvdata(pdev, ddata); + + ret = ocores_pwm_runtime_resume(dev); + if (ret) + return ret; + + ret = reset_control_deassert(ddata->rst); + if (ret) + goto err_clk_disable; + + ret = pm_runtime_set_active(dev); + if (ret) + goto err_reset_assert; + + pm_runtime_enable(dev); + + ret = devm_add_action_or_reset(dev, ocores_pwm_pm_disable, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to add pm disable action\n"); + + pm_runtime_get_noresume(dev); + + writel(0, ddata->base + OCPWM_CTRL); + + pm_runtime_put(dev); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Could not register PWM chip\n"); + + return 0; + +err_reset_assert: + reset_control_assert(ddata->rst); +err_clk_disable: + ocores_pwm_runtime_suspend(dev); + return dev_err_probe(dev, ret, "Failed to init pwm power\n"); +} + +static const struct of_device_id ocores_pwm_of_match[] = { + { .compatible = "opencores,pwm-v1" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ocores_pwm_of_match); + +static struct platform_driver ocores_pwm_driver = { + .probe = ocores_pwm_probe, + .driver = { + .name = "ocores-pwm", + .of_match_table = ocores_pwm_of_match, + .pm = pm_ptr(&ocores_pwm_pm_ops), + }, +}; +module_platform_driver(ocores_pwm_driver); + +MODULE_AUTHOR("Jieqin Chen"); +MODULE_AUTHOR("Hal Feng "); +MODULE_DESCRIPTION("OpenCores PTC PWM driver"); +MODULE_LICENSE("GPL");