[RESEND v22 3/3] pwm: Add OpenCores PTC PWM driver

Hal Feng hal.feng at starfivetech.com
Wed Aug 12 01:52:46 PDT 2026


> On 26.08.10 11:07, Hal Feng wrote:
> 
> Add PWM driver for OpenCores PTC IP core.
> 
> Signed-off-by: Hal Feng <hal.feng at starfivetech.com>

Hi, Uwe,

As all AI comments have been addressed, could you please help review this series? @Uwe Kleine-König

Best regards,
Hal

> ---
>  MAINTAINERS              |   6 +
>  drivers/pwm/Kconfig      |  12 ++
>  drivers/pwm/Makefile     |   1 +
>  drivers/pwm/pwm-ocores.c | 298
> +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 317 insertions(+)
>  create mode 100644 drivers/pwm/pwm-ocores.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8014b9f8253e..7886fd9a4ac0 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20273,6 +20273,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 <hal.feng at starfivetech.com>
> +S:	Supported
> +F:	Documentation/devicetree/bindings/pwm/opencores,pwm.yaml
> +F:	drivers/pwm/pwm-ocores.c
> +
>  OPENRISC ARCHITECTURE
>  M:	Jonas Bonn <jonas at southpole.se>
>  M:	Stefan Kristiansson <stefan.kristiansson at saunalahti.fi>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> e8886a9b64d9..1268c07d07f1 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -546,6 +546,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
> 5630a521a7cf..4d2d14c4852f 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -48,6 +48,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 000000000000..f873089a385b
> --- /dev/null
> +++ b/drivers/pwm/pwm-ocores.c
> @@ -0,0 +1,298 @@
> +// 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 / non-zero duty_cycle 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 <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/pwm.h>
> +#include <linux/reset.h>
> +
> +#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)
> +
> +#define OCPWM_NUM_SAVED_REGS	3
> +
> +struct ocores_pwm_device {
> +	void __iomem *base;
> +	struct clk *clk;
> +	unsigned long clk_rate;
> +	struct reset_control *rst;
> +	u32 saved_regs[OCPWM_NUM_SAVED_REGS];
> +};
> +
> +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);
> +	bool was_enabled = pwm_is_enabled(pwm);
> +	u64 period_data, duty_data;
> +	int ret;
> +
> +	if (state->polarity != PWM_POLARITY_INVERSED)
> +		return -EINVAL;
> +
> +	if (!state->enabled) {
> +		if (was_enabled) {
> +			writel(0, ddata->base + OCPWM_CTRL);
> +			pm_runtime_put(pwmchip_parent(chip));
> +		}
> +		return 0;
> +	}
> +
> +	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;
> +
> +	if (!period_data)
> +		return -EINVAL;
> +
> +	if (!was_enabled) {
> +		ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	writel(0, ddata->base + OCPWM_CTRL);
> +	writel(OCPWM_CTRL_RST, ddata->base + OCPWM_CTRL);
> +
> +	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 int __maybe_unused ocores_pwm_suspend(struct device *dev) {
> +	struct ocores_pwm_device *ddata = dev_get_drvdata(dev);
> +	int ret, i;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	for (i = 0; i < OCPWM_NUM_SAVED_REGS; i++)
> +		ddata->saved_regs[i] = readl(ddata->base + 4 + 4 * i);
> +
> +	pm_runtime_put_sync(dev);
> +
> +	return pm_runtime_force_suspend(dev);
> +}
> +
> +static int __maybe_unused ocores_pwm_resume(struct device *dev) {
> +	struct ocores_pwm_device *ddata = dev_get_drvdata(dev);
> +	int ret, i;
> +
> +	ret = pm_runtime_force_resume(dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	writel(0, ddata->base + OCPWM_CTRL);
> +	writel(OCPWM_CTRL_RST, ddata->base + OCPWM_CTRL);
> +	for (i = 0; i < OCPWM_NUM_SAVED_REGS; i++)
> +		writel(ddata->saved_regs[i], ddata->base + 4 + 4 * i);
> +
> +	pm_runtime_put_sync(dev);
> +
> +	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(ocores_pwm_suspend,
> ocores_pwm_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)) {
> +		/* Balance the reference held while the PWM is enabled. */
> +		if (readl(ddata->base + OCPWM_CTRL) & OCPWM_CTRL_EN)
> +			pm_runtime_put_noidle(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 PTR_ERR(chip);
> +
> +	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_get_noresume(dev);
> +	pm_runtime_enable(dev);
> +
> +	if (!(readl(ddata->base + OCPWM_CTRL) & OCPWM_CTRL_EN))
> +		pm_runtime_put_sync(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");
> +
> +	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 <hal.feng at starfivetech.com>");
> +MODULE_DESCRIPTION("OpenCores PTC PWM driver");
> MODULE_LICENSE("GPL");
> --
> 2.43.2




More information about the linux-riscv mailing list