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>; 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/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>; 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");