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

Hal Feng <[email protected]>
Newsgroups org.kernel.vger.linux-pwm,org.infradead.lists.linux-riscv,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel
Message-ID <ZQ2PR01MB130705520872CE417DC5B261E6DC2@ZQ2PR01MB1307.CHNPR01.prod.partner.outlook.cn>
> On 26.08.10 11:07, Hal Feng wrote:
> 
> Add PWM driver for OpenCores PTC IP core.
> 
> Signed-off-by: Hal Feng <[email protected]>

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 <[email protected]>
> +S:	Supported
> +F:	Documentation/devicetree/bindings/pwm/opencores,pwm.yaml
> +F:	drivers/pwm/pwm-ocores.c
> +
>  OPENRISC ARCHITECTURE
>  M:	Jonas Bonn <[email protected]>
>  M:	Stefan Kristiansson <[email protected]>
> 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 <[email protected]>");
> +MODULE_DESCRIPTION("OpenCores PTC PWM driver");
> MODULE_LICENSE("GPL");
> --
> 2.43.2
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.